Annotation of src/usr.bin/rcs/rcs.c, Revision 1.88
1.88 ! joris 1: /* $OpenBSD: rcs.c,v 1.87 2019/01/09 17:55:28 joris 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;
1.87 joris 694: struct rcs_delta *rdp;
695:
696: if ((rdp = rcs_findrev(file, rev)) == NULL) {
697: rcs_errno = RCS_ERR_NOENT;
698: return (-1);
699: }
1.1 joris 700:
701: /* first look for duplication */
702: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
703: if (strcmp(lkp->rl_name, user) == 0 &&
704: rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
705: rcs_errno = RCS_ERR_DUPENT;
706: return (-1);
707: }
708: }
709:
710: lkp = xmalloc(sizeof(*lkp));
711: lkp->rl_name = xstrdup(user);
712: lkp->rl_num = rcsnum_alloc();
713: rcsnum_cpy(rev, lkp->rl_num, 0);
714:
1.87 joris 715: free(rdp->rd_locker);
716: rdp->rd_locker = xstrdup(user);
717:
1.1 joris 718: TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
719:
720: /* not synced anymore */
721: file->rf_flags &= ~RCS_SYNCED;
722: return (0);
723: }
724:
725:
726: /*
727: * rcs_lock_remove()
728: *
729: * Remove the RCS lock on revision <rev>.
730: * Returns 0 on success, or -1 on failure.
731: */
732: int
733: rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
734: {
735: struct rcs_lock *lkp;
1.87 joris 736: struct rcs_delta *rdp;
737:
738: if ((rdp = rcs_findrev(file, rev)) == NULL) {
739: rcs_errno = RCS_ERR_NOENT;
740: return (-1);
741: }
1.1 joris 742:
743: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
744: if (strcmp(lkp->rl_name, user) == 0 &&
745: rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
746: break;
747: }
748:
749: if (lkp == NULL) {
750: rcs_errno = RCS_ERR_NOENT;
751: return (-1);
752: }
753:
754: TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
755: rcsnum_free(lkp->rl_num);
1.83 nicm 756: free(lkp->rl_name);
757: free(lkp);
1.87 joris 758:
759: free(rdp->rd_locker);
760: rdp->rd_locker = NULL;
1.1 joris 761:
762: /* not synced anymore */
763: file->rf_flags &= ~RCS_SYNCED;
764: return (0);
765: }
766:
767: /*
768: * rcs_desc_set()
769: *
770: * Set the description for the RCS file <file>.
771: */
772: void
773: rcs_desc_set(RCSFILE *file, const char *desc)
774: {
775: char *tmp;
776:
777: tmp = xstrdup(desc);
1.83 nicm 778: free(file->rf_desc);
1.1 joris 779: file->rf_desc = tmp;
780: file->rf_flags &= ~RCS_SYNCED;
781: }
782:
783: /*
784: * rcs_comment_set()
785: *
786: * Set the comment leader for the RCS file <file>.
787: */
788: void
789: rcs_comment_set(RCSFILE *file, const char *comment)
790: {
791: char *tmp;
792:
793: tmp = xstrdup(comment);
1.83 nicm 794: free(file->rf_comment);
1.1 joris 795: file->rf_comment = tmp;
796: file->rf_flags &= ~RCS_SYNCED;
797: }
798:
799: int
800: rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
801: {
802: char op, *ep;
803: struct rcs_line *lp, *dlp, *ndlp;
804: int i, lineno, nbln;
1.35 niallo 805: u_char tmp;
1.1 joris 806:
807: dlp = TAILQ_FIRST(&(dlines->l_lines));
808: lp = TAILQ_FIRST(&(plines->l_lines));
809:
810: /* skip first bogus line */
811: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
812: lp = TAILQ_NEXT(lp, l_list)) {
1.35 niallo 813: if (lp->l_len < 2)
814: errx(1, "line too short, RCS patch seems broken");
1.1 joris 815: op = *(lp->l_line);
1.35 niallo 816: /* NUL-terminate line buffer for strtol() safety. */
817: tmp = lp->l_line[lp->l_len - 1];
818: lp->l_line[lp->l_len - 1] = '\0';
1.1 joris 819: lineno = (int)strtol((lp->l_line + 1), &ep, 10);
820: if (lineno > dlines->l_nblines || lineno < 0 ||
821: *ep != ' ')
822: errx(1, "invalid line specification in RCS patch");
823: ep++;
824: nbln = (int)strtol(ep, &ep, 10);
1.35 niallo 825: /* Restore the last byte of the buffer */
826: lp->l_line[lp->l_len - 1] = tmp;
827: if (nbln < 0)
1.1 joris 828: errx(1,
829: "invalid line number specification in RCS patch");
830:
831: /* find the appropriate line */
832: for (;;) {
833: if (dlp == NULL)
834: break;
835: if (dlp->l_lineno == lineno)
836: break;
837: if (dlp->l_lineno > lineno) {
1.58 ray 838: dlp = TAILQ_PREV(dlp, tqh, l_list);
1.1 joris 839: } else if (dlp->l_lineno < lineno) {
840: if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
841: ndlp->l_lineno > lineno)
842: break;
843: dlp = ndlp;
844: }
845: }
846: if (dlp == NULL)
847: errx(1, "can't find referenced line in RCS patch");
848:
849: if (op == 'd') {
850: for (i = 0; (i < nbln) && (dlp != NULL); i++) {
851: ndlp = TAILQ_NEXT(dlp, l_list);
852: TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
1.83 nicm 853: free(dlp);
1.1 joris 854: dlp = ndlp;
855: /* last line is gone - reset dlp */
856: if (dlp == NULL) {
857: ndlp = TAILQ_LAST(&(dlines->l_lines),
1.58 ray 858: tqh);
1.1 joris 859: dlp = ndlp;
860: }
861: }
862: } else if (op == 'a') {
863: for (i = 0; i < nbln; i++) {
864: ndlp = lp;
865: lp = TAILQ_NEXT(lp, l_list);
866: if (lp == NULL)
867: errx(1, "truncated RCS patch");
868: TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
869: TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
870: lp, l_list);
871: dlp = lp;
872:
873: /* we don't want lookup to block on those */
874: lp->l_lineno = lineno;
875:
876: lp = ndlp;
877: }
878: } else
879: errx(1, "unknown RCS patch operation `%c'", op);
880:
881: /* last line of the patch, done */
882: if (lp->l_lineno == plines->l_nblines)
883: break;
884: }
885:
886: /* once we're done patching, rebuild the line numbers */
887: lineno = 0;
888: TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
889: lp->l_lineno = lineno++;
890: dlines->l_nblines = lineno - 1;
891:
892: return (0);
893: }
894:
895: /*
896: * rcs_getrev()
897: *
898: * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
899: * returned buffer is dynamically allocated and should be released using
1.59 ray 900: * buf_free() once the caller is done using it.
1.1 joris 901: */
1.61 tobias 902: BUF *
1.1 joris 903: rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
904: {
905: u_int i, numlen;
1.6 niallo 906: int isbranch, lookonbranch, found;
1.35 niallo 907: size_t dlen, plen, len;
1.1 joris 908: RCSNUM *crev, *rev, *brev;
1.35 niallo 909: BUF *rbuf;
1.1 joris 910: struct rcs_delta *rdp = NULL;
911: struct rcs_branch *rb;
1.35 niallo 912: u_char *data, *patch;
1.1 joris 913:
914: if (rfp->rf_head == NULL)
915: return (NULL);
916:
917: if (frev == RCS_HEAD_REV)
918: rev = rfp->rf_head;
919: else
920: rev = frev;
921:
922: /* XXX rcsnum_cmp() */
923: for (i = 0; i < rfp->rf_head->rn_len; i++) {
924: if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
925: rcs_errno = RCS_ERR_NOENT;
926: return (NULL);
927: }
928: }
929:
1.54 jj 930: /* No matter what, we'll need everything parsed up until the description
931: so go for it. */
1.68 tobias 932: if (rcsparse_deltas(rfp, NULL))
933: return (NULL);
1.1 joris 934:
935: rdp = rcs_findrev(rfp, rfp->rf_head);
936: if (rdp == NULL) {
937: warnx("failed to get RCS HEAD revision");
938: return (NULL);
939: }
940:
941: if (rdp->rd_tlen == 0)
1.68 tobias 942: if (rcsparse_deltatexts(rfp, rfp->rf_head))
943: return (NULL);
1.1 joris 944:
945: len = rdp->rd_tlen;
946: if (len == 0) {
1.60 ray 947: rbuf = buf_alloc(1);
1.59 ray 948: buf_empty(rbuf);
1.1 joris 949: return (rbuf);
950: }
951:
1.60 ray 952: rbuf = buf_alloc(len);
1.59 ray 953: buf_append(rbuf, rdp->rd_text, len);
1.1 joris 954:
955: isbranch = 0;
956: brev = NULL;
957:
958: /*
959: * If a branch was passed, get the latest revision on it.
960: */
961: if (RCSNUM_ISBRANCH(rev)) {
962: brev = rev;
963: rdp = rcs_findrev(rfp, rev);
1.64 nicm 964: if (rdp == NULL) {
965: buf_free(rbuf);
1.1 joris 966: return (NULL);
1.64 nicm 967: }
1.1 joris 968:
969: rev = rdp->rd_num;
970: } else {
971: if (RCSNUM_ISBRANCHREV(rev)) {
972: brev = rcsnum_revtobr(rev);
973: isbranch = 1;
974: }
975: }
976:
977: lookonbranch = 0;
978: crev = NULL;
979:
980: /* Apply patches backwards to get the right version.
981: */
982: do {
1.6 niallo 983: found = 0;
1.26 deraadt 984:
1.1 joris 985: if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
986: break;
987:
988: if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
989: !TAILQ_EMPTY(&(rdp->rd_branches)))
990: lookonbranch = 1;
991:
992: if (isbranch && lookonbranch == 1) {
993: lookonbranch = 0;
994: TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
995: /* XXX rcsnum_cmp() is totally broken for
996: * this purpose.
997: */
1.82 deraadt 998: numlen = MINIMUM(brev->rn_len,
1.50 tobias 999: rb->rb_num->rn_len - 1);
1.1 joris 1000: for (i = 0; i < numlen; i++) {
1001: if (rb->rb_num->rn_id[i] !=
1002: brev->rn_id[i])
1003: break;
1004: }
1005:
1006: if (i == numlen) {
1007: crev = rb->rb_num;
1.6 niallo 1008: found = 1;
1.1 joris 1009: break;
1010: }
1011: }
1.6 niallo 1012: if (found == 0)
1013: crev = rdp->rd_next;
1.1 joris 1014: } else {
1015: crev = rdp->rd_next;
1016: }
1017:
1018: rdp = rcs_findrev(rfp, crev);
1019: if (rdp == NULL) {
1.59 ray 1020: buf_free(rbuf);
1.1 joris 1021: return (NULL);
1022: }
1023:
1.35 niallo 1024: plen = rdp->rd_tlen;
1.59 ray 1025: dlen = buf_len(rbuf);
1.35 niallo 1026: patch = rdp->rd_text;
1.59 ray 1027: data = buf_release(rbuf);
1.1 joris 1028: /* check if we have parsed this rev's deltatext */
1029: if (rdp->rd_tlen == 0)
1.68 tobias 1030: if (rcsparse_deltatexts(rfp, rdp->rd_num))
1031: return (NULL);
1.1 joris 1032:
1.35 niallo 1033: rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
1.83 nicm 1034: free(data);
1.1 joris 1035:
1036: if (rbuf == NULL)
1037: break;
1038: } while (rcsnum_cmp(crev, rev, 0) != 0);
1039:
1040: return (rbuf);
1041: }
1.46 xsa 1042:
1043: void
1044: rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1045: {
1046: struct rcs_lines *plines;
1047: struct rcs_line *lp;
1.73 jasper 1048: int added, i, nbln, removed;
1.46 xsa 1049: char op, *ep;
1050: u_char tmp;
1051:
1052: added = removed = 0;
1053:
1054: plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1055: lp = TAILQ_FIRST(&(plines->l_lines));
1056:
1057: /* skip first bogus line */
1058: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1059: lp = TAILQ_NEXT(lp, l_list)) {
1060: if (lp->l_len < 2)
1061: errx(1,
1062: "line too short, RCS patch seems broken");
1063: op = *(lp->l_line);
1064: /* NUL-terminate line buffer for strtol() safety. */
1065: tmp = lp->l_line[lp->l_len - 1];
1066: lp->l_line[lp->l_len - 1] = '\0';
1.74 djm 1067: (void)strtol((lp->l_line + 1), &ep, 10);
1.46 xsa 1068: ep++;
1069: nbln = (int)strtol(ep, &ep, 10);
1070: /* Restore the last byte of the buffer */
1071: lp->l_line[lp->l_len - 1] = tmp;
1072: if (nbln < 0)
1073: errx(1, "invalid line number specification "
1074: "in RCS patch");
1075:
1076: if (op == 'a') {
1077: added += nbln;
1078: for (i = 0; i < nbln; i++) {
1079: lp = TAILQ_NEXT(lp, l_list);
1080: if (lp == NULL)
1081: errx(1, "truncated RCS patch");
1082: }
1083: } else if (op == 'd')
1084: removed += nbln;
1085: else
1086: errx(1, "unknown RCS patch operation '%c'", op);
1087: }
1.47 tobias 1088:
1089: rcs_freelines(plines);
1.46 xsa 1090:
1091: *ladded = added;
1092: *lremoved = removed;
1093: }
1.1 joris 1094:
1095: /*
1096: * rcs_rev_add()
1097: *
1098: * Add a revision to the RCS file <rf>. The new revision's number can be
1099: * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1100: * new revision will have a number equal to the previous head revision plus
1101: * one). The <msg> argument specifies the log message for that revision, and
1102: * <date> specifies the revision's date (a value of -1 is
1103: * equivalent to using the current time).
1.57 ray 1104: * If <author> is NULL, set the author for this revision to the current user.
1.1 joris 1105: * Returns 0 on success, or -1 on failure.
1106: */
1107: int
1108: rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1.57 ray 1109: const char *author)
1.1 joris 1110: {
1111: time_t now;
1112: struct passwd *pw;
1113: struct rcs_delta *ordp, *rdp;
1114:
1115: if (rev == RCS_HEAD_REV) {
1116: if (rf->rf_flags & RCS_CREATE) {
1117: if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1118: return (-1);
1.62 tobias 1119: rf->rf_head = rev;
1.1 joris 1120: } else {
1121: rev = rcsnum_inc(rf->rf_head);
1122: }
1123: } else {
1124: if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1125: rcs_errno = RCS_ERR_DUPENT;
1126: return (-1);
1127: }
1128: }
1129:
1130: rdp = xcalloc(1, sizeof(*rdp));
1131:
1132: TAILQ_INIT(&(rdp->rd_branches));
1133:
1134: rdp->rd_num = rcsnum_alloc();
1135: rcsnum_cpy(rev, rdp->rd_num, 0);
1136:
1137: rdp->rd_next = rcsnum_alloc();
1138:
1139: if (!(rf->rf_flags & RCS_CREATE)) {
1140: /* next should point to the previous HEAD */
1141: ordp = TAILQ_FIRST(&(rf->rf_delta));
1142: rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1143: }
1144:
1.57 ray 1145: if (!author && !(author = getlogin())) {
1146: if (!(pw = getpwuid(getuid())))
1147: errx(1, "getpwuid failed");
1148: author = pw->pw_name;
1149: }
1150: rdp->rd_author = xstrdup(author);
1.1 joris 1151: rdp->rd_state = xstrdup(RCS_STATE_EXP);
1152: rdp->rd_log = xstrdup(msg);
1153:
1154: if (date != (time_t)(-1))
1155: now = date;
1156: else
1157: time(&now);
1158: gmtime_r(&now, &(rdp->rd_date));
1159:
1160: TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1161: rf->rf_ndelta++;
1162:
1163: /* not synced anymore */
1164: rf->rf_flags &= ~RCS_SYNCED;
1165:
1166: return (0);
1167: }
1168:
1169: /*
1170: * rcs_rev_remove()
1171: *
1172: * Remove the revision whose number is <rev> from the RCS file <rf>.
1173: */
1174: int
1175: rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1176: {
1.12 niallo 1177: char *path_tmp1, *path_tmp2;
1.1 joris 1178: struct rcs_delta *rdp, *prevrdp, *nextrdp;
1.12 niallo 1179: BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
1180:
1181: nextrdp = prevrdp = NULL;
1.17 niallo 1182: path_tmp1 = path_tmp2 = NULL;
1.1 joris 1183:
1184: if (rev == RCS_HEAD_REV)
1185: rev = rf->rf_head;
1186:
1187: /* do we actually have that revision? */
1188: if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1189: rcs_errno = RCS_ERR_NOENT;
1190: return (-1);
1191: }
1192:
1193: /*
1194: * This is confusing, the previous delta is next in the TAILQ list.
1195: * the next delta is the previous one in the TAILQ list.
1196: *
1197: * When the HEAD revision got specified, nextrdp will be NULL.
1198: * When the first revision got specified, prevrdp will be NULL.
1199: */
1200: prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1.58 ray 1201: nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1.1 joris 1202:
1.12 niallo 1203: newdeltatext = prevbuf = nextbuf = NULL;
1.1 joris 1204:
1205: if (prevrdp != NULL) {
1206: if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
1207: errx(1, "error getting revision");
1208: }
1209:
1210: if (prevrdp != NULL && nextrdp != NULL) {
1211: if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
1212: errx(1, "error getting revision");
1213:
1.60 ray 1214: newdiff = buf_alloc(64);
1.1 joris 1215:
1216: /* calculate new diff */
1.7 xsa 1217: (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1218: buf_write_stmp(nextbuf, path_tmp1);
1219: buf_free(nextbuf);
1.1 joris 1220:
1.7 xsa 1221: (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
1.59 ray 1222: buf_write_stmp(prevbuf, path_tmp2);
1223: buf_free(prevbuf);
1.1 joris 1224:
1225: diff_format = D_RCSDIFF;
1.53 ray 1226: if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1.41 ray 1227: errx(1, "diffreg failed");
1.1 joris 1228:
1.12 niallo 1229: newdeltatext = newdiff;
1.1 joris 1230: } else if (nextrdp == NULL && prevrdp != NULL) {
1.12 niallo 1231: newdeltatext = prevbuf;
1.1 joris 1232: }
1233:
1234: if (newdeltatext != NULL) {
1235: if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1236: errx(1, "error setting new deltatext");
1237: }
1238:
1239: TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1240:
1241: /* update pointers */
1242: if (prevrdp != NULL && nextrdp != NULL) {
1243: rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1244: } else if (prevrdp != NULL) {
1245: if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1246: errx(1, "rcs_head_set failed");
1247: } else if (nextrdp != NULL) {
1248: rcsnum_free(nextrdp->rd_next);
1249: nextrdp->rd_next = rcsnum_alloc();
1250: } else {
1251: rcsnum_free(rf->rf_head);
1252: rf->rf_head = NULL;
1253: }
1254:
1255: rf->rf_ndelta--;
1256: rf->rf_flags &= ~RCS_SYNCED;
1257:
1258: rcs_freedelta(rdp);
1259:
1.83 nicm 1260: free(path_tmp1);
1261: free(path_tmp2);
1.1 joris 1262:
1263: return (0);
1264: }
1265:
1266: /*
1267: * rcs_findrev()
1268: *
1269: * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1270: * The revision number is given in <rev>.
1271: *
1272: * If the given revision is a branch number, we translate it into the latest
1273: * revision on the branch.
1274: *
1275: * Returns a pointer to the delta on success, or NULL on failure.
1276: */
1277: struct rcs_delta *
1278: rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1279: {
1280: u_int cmplen;
1281: struct rcs_delta *rdp;
1282: RCSNUM *brev, *frev;
1283:
1284: /*
1285: * We need to do more parsing if the last revision in the linked list
1286: * is greater than the requested revision.
1287: */
1288: rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1289: if (rdp == NULL ||
1290: rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1.68 tobias 1291: if (rcsparse_deltas(rfp, rev))
1292: return (NULL);
1.1 joris 1293: }
1294:
1295: /*
1296: * Translate a branch into the latest revision on the branch itself.
1297: */
1298: if (RCSNUM_ISBRANCH(rev)) {
1299: brev = rcsnum_brtorev(rev);
1300: frev = brev;
1301: for (;;) {
1302: rdp = rcs_findrev(rfp, frev);
1303: if (rdp == NULL)
1304: return (NULL);
1305:
1306: if (rdp->rd_next->rn_len == 0)
1307: break;
1308:
1309: frev = rdp->rd_next;
1310: }
1311:
1312: rcsnum_free(brev);
1313: return (rdp);
1314: }
1315:
1316: cmplen = rev->rn_len;
1317:
1318: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1319: if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
1320: return (rdp);
1321: }
1322:
1323: return (NULL);
1324: }
1325:
1326: /*
1327: * rcs_kwexp_set()
1328: *
1329: * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1330: */
1331: void
1332: rcs_kwexp_set(RCSFILE *file, int mode)
1333: {
1334: int i;
1335: char *tmp, buf[8] = "";
1336:
1337: if (RCS_KWEXP_INVAL(mode))
1338: return;
1339:
1340: i = 0;
1341: if (mode == RCS_KWEXP_NONE)
1342: buf[0] = 'b';
1343: else if (mode == RCS_KWEXP_OLD)
1344: buf[0] = 'o';
1345: else {
1346: if (mode & RCS_KWEXP_NAME)
1347: buf[i++] = 'k';
1348: if (mode & RCS_KWEXP_VAL)
1349: buf[i++] = 'v';
1350: if (mode & RCS_KWEXP_LKR)
1351: buf[i++] = 'l';
1352: }
1353:
1354: tmp = xstrdup(buf);
1.83 nicm 1355: free(file->rf_expand);
1.1 joris 1356: file->rf_expand = tmp;
1357: /* not synced anymore */
1358: file->rf_flags &= ~RCS_SYNCED;
1359: }
1360:
1361: /*
1362: * rcs_kwexp_get()
1363: *
1364: * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1365: */
1366: int
1367: rcs_kwexp_get(RCSFILE *file)
1368: {
1.71 tobias 1369: if (file->rf_expand == NULL)
1370: return (RCS_KWEXP_DEFAULT);
1371:
1372: return (rcs_kflag_get(file->rf_expand));
1.1 joris 1373: }
1374:
1375: /*
1376: * rcs_kflag_get()
1377: *
1378: * Get the keyword expansion mode from a set of character flags given in
1379: * <flags> and return the appropriate flag mask. In case of an error, the
1380: * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1381: */
1382: int
1383: rcs_kflag_get(const char *flags)
1384: {
1385: int fl;
1386: size_t len;
1387: const char *fp;
1388:
1.71 tobias 1389: if (flags == NULL || !(len = strlen(flags)))
1390: return (RCS_KWEXP_ERR);
1391:
1.1 joris 1392: fl = 0;
1393: for (fp = flags; *fp != '\0'; fp++) {
1394: if (*fp == 'k')
1395: fl |= RCS_KWEXP_NAME;
1396: else if (*fp == 'v')
1397: fl |= RCS_KWEXP_VAL;
1398: else if (*fp == 'l')
1399: fl |= RCS_KWEXP_LKR;
1400: else if (*fp == 'o') {
1401: if (len != 1)
1402: fl |= RCS_KWEXP_ERR;
1403: fl |= RCS_KWEXP_OLD;
1404: } else if (*fp == 'b') {
1405: if (len != 1)
1406: fl |= RCS_KWEXP_ERR;
1.71 tobias 1407: fl |= RCS_KWEXP_NONE;
1.1 joris 1408: } else /* unknown letter */
1409: fl |= RCS_KWEXP_ERR;
1410: }
1411:
1412: return (fl);
1413: }
1414:
1415: /*
1416: * rcs_freedelta()
1417: *
1418: * Free the contents of a delta structure.
1419: */
1420: static void
1421: rcs_freedelta(struct rcs_delta *rdp)
1422: {
1423: struct rcs_branch *rb;
1424:
1.84 nicm 1425: rcsnum_free(rdp->rd_num);
1426: rcsnum_free(rdp->rd_next);
1.1 joris 1427:
1.83 nicm 1428: free(rdp->rd_author);
1429: free(rdp->rd_locker);
1430: free(rdp->rd_state);
1431: free(rdp->rd_log);
1432: free(rdp->rd_text);
1.1 joris 1433:
1434: while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1435: TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1436: rcsnum_free(rb->rb_num);
1.83 nicm 1437: free(rb);
1.1 joris 1438: }
1439:
1.83 nicm 1440: free(rdp);
1.1 joris 1441: }
1442:
1443: /*
1444: * rcs_strprint()
1445: *
1446: * Output an RCS string <str> of size <slen> to the stream <stream>. Any
1447: * '@' characters are escaped. Otherwise, the string can contain arbitrary
1448: * binary data.
1449: */
1450: static void
1451: rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1452: {
1453: const u_char *ap, *ep, *sp;
1454:
1455: if (slen == 0)
1456: return;
1457:
1458: ep = str + slen - 1;
1459:
1460: for (sp = str; sp <= ep;) {
1461: ap = memchr(sp, '@', ep - sp);
1462: if (ap == NULL)
1463: ap = ep;
1464: (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1465:
1466: if (*ap == '@')
1467: putc('@', stream);
1468: sp = ap + 1;
1469: }
1470: }
1471:
1472: /*
1473: * rcs_expand_keywords()
1474: *
1475: * Return expansion any RCS keywords in <data>
1476: *
1477: * On error, return NULL.
1478: */
1.12 niallo 1479: static BUF *
1.77 nicm 1480: rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
1.1 joris 1481: {
1.28 ray 1482: BUF *newbuf;
1.77 nicm 1483: u_char *c, *kw, *fin;
1.82 deraadt 1484: char buf[256], *tmpf, resolved[PATH_MAX], *rcsfile;
1.77 nicm 1485: u_char *line, *line2;
1486: u_int i, j;
1.1 joris 1487: int kwtype;
1.77 nicm 1488: int found;
1.1 joris 1489: struct tm tb;
1490:
1491: tb = rdp->rd_date;
1492: if (timezone_flag != NULL)
1493: rcs_set_tz(timezone_flag, rdp, &tb);
1494:
1.77 nicm 1495: if (realpath(rcsfile_in, resolved) == NULL)
1496: rcsfile = rcsfile_in;
1497: else
1498: rcsfile = resolved;
1.12 niallo 1499:
1.77 nicm 1500: newbuf = buf_alloc(buf_len(bp));
1.18 niallo 1501:
1.1 joris 1502: /*
1503: * Keyword formats:
1504: * $Keyword$
1505: * $Keyword: value$
1506: */
1.77 nicm 1507: c = buf_get(bp);
1508: fin = c + buf_len(bp);
1509: /* Copying to newbuf is deferred until the first keyword. */
1510: found = 0;
1511:
1512: while (c < fin) {
1513: kw = memchr(c, '$', fin - c);
1514: if (kw == NULL)
1515: break;
1516: ++kw;
1517: if (found) {
1518: /* Copy everything up to and including the $. */
1519: buf_append(newbuf, c, kw - c);
1520: }
1521: c = kw;
1522: /* c points after the $ now. */
1523: if (c == fin)
1524: break;
1525: if (!isalpha(*c)) /* all valid keywords start with a letter */
1526: continue;
1.1 joris 1527:
1.77 nicm 1528: for (i = 0; i < RCS_NKWORDS; ++i) {
1529: size_t kwlen;
1.28 ray 1530:
1.77 nicm 1531: kwlen = strlen(rcs_expkw[i].kw_str);
1532: /*
1533: * kwlen must be less than clen since clen includes
1534: * either a terminating `$' or a `:'.
1535: */
1536: if (c + kwlen < fin &&
1537: memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
1538: (c[kwlen] == '$' || c[kwlen] == ':')) {
1539: c += kwlen;
1540: break;
1541: }
1542: }
1543: if (i == RCS_NKWORDS)
1544: continue;
1545: kwtype = rcs_expkw[i].kw_type;
1546:
1547: /*
1548: * If the next character is ':' we need to look for an '$'
1549: * before the end of the line to be sure it is in fact a
1550: * keyword.
1551: */
1552: if (*c == ':') {
1553: for (; c < fin; ++c) {
1554: if (*c == '$' || *c == '\n')
1.1 joris 1555: break;
1556: }
1557:
1.77 nicm 1558: if (*c != '$') {
1559: if (found)
1560: buf_append(newbuf, kw, c - kw);
1.1 joris 1561: continue;
1562: }
1.77 nicm 1563: }
1564: ++c;
1.1 joris 1565:
1.77 nicm 1566: if (!found) {
1567: found = 1;
1568: /* Copy everything up to and including the $. */
1569: buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
1570: }
1571:
1572: if (mode & RCS_KWEXP_NAME) {
1573: buf_puts(newbuf, rcs_expkw[i].kw_str);
1574: if (mode & RCS_KWEXP_VAL)
1575: buf_puts(newbuf, ": ");
1576: }
1.1 joris 1577:
1.77 nicm 1578: /* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
1579: if (mode & RCS_KWEXP_VAL) {
1580: if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
1581: if ((kwtype & RCS_KW_FULLPATH) ||
1582: (tmpf = strrchr(rcsfile, '/')) == NULL)
1583: buf_puts(newbuf, rcsfile);
1584: else
1585: buf_puts(newbuf, tmpf + 1);
1586: buf_putc(newbuf, ' ');
1.1 joris 1587: }
1588:
1.77 nicm 1589: if (kwtype & RCS_KW_REVISION) {
1590: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1591: buf_puts(newbuf, buf);
1592: buf_putc(newbuf, ' ');
1593: }
1.1 joris 1594:
1.77 nicm 1595: if (kwtype & RCS_KW_DATE) {
1596: strftime(buf, sizeof(buf),
1597: "%Y/%m/%d %H:%M:%S ", &tb);
1598: buf_puts(newbuf, buf);
1599: }
1.31 ray 1600:
1.77 nicm 1601: if (kwtype & RCS_KW_AUTHOR) {
1602: buf_puts(newbuf, rdp->rd_author);
1603: buf_putc(newbuf, ' ');
1.1 joris 1604: }
1605:
1.77 nicm 1606: if (kwtype & RCS_KW_STATE) {
1607: buf_puts(newbuf, rdp->rd_state);
1608: buf_putc(newbuf, ' ');
1609: }
1.1 joris 1610:
1.77 nicm 1611: /* Order does not matter anymore below. */
1612: if (kwtype & RCS_KW_SOURCE) {
1613: buf_puts(newbuf, rcsfile);
1614: buf_putc(newbuf, ' ');
1.85 schwarze 1615: }
1616:
1617: if (kwtype & RCS_KW_MDOCDATE) {
1618: strftime(buf, sizeof(buf), "%B", &tb);
1619: buf_puts(newbuf, buf);
1620: /* Only one blank before single-digit day. */
1621: snprintf(buf, sizeof(buf), " %d", tb.tm_mday);
1622: buf_puts(newbuf, buf);
1623: strftime(buf, sizeof(buf), " %Y ", &tb);
1624: buf_puts(newbuf, buf);
1.77 nicm 1625: }
1.31 ray 1626:
1.77 nicm 1627: if (kwtype & RCS_KW_NAME)
1628: buf_putc(newbuf, ' ');
1.1 joris 1629:
1.77 nicm 1630: if ((kwtype & RCS_KW_LOCKER)) {
1631: if (rdp->rd_locker) {
1632: buf_puts(newbuf, rdp->rd_locker);
1633: buf_putc(newbuf, ' ');
1.1 joris 1634: }
1.77 nicm 1635: }
1636: }
1.1 joris 1637:
1.77 nicm 1638: /* End the expansion. */
1639: if (mode & RCS_KWEXP_NAME)
1640: buf_putc(newbuf, '$');
1.31 ray 1641:
1.77 nicm 1642: if (kwtype & RCS_KW_LOG) {
1643: line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
1644: if (line == NULL)
1645: line = buf_get(bp);
1646: else
1647: ++line;
1648: line2 = kw - 1;
1649: while (line2 > line && line2[-1] == ' ')
1650: --line2;
1651:
1652: buf_putc(newbuf, '\n');
1653: buf_append(newbuf, line, kw - 1 - line);
1654: buf_puts(newbuf, "Revision ");
1655: rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1656: buf_puts(newbuf, buf);
1657: buf_puts(newbuf, " ");
1658: strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
1659: buf_puts(newbuf, buf);
1660:
1661: buf_puts(newbuf, " ");
1662: buf_puts(newbuf, rdp->rd_author);
1663: buf_putc(newbuf, '\n');
1664:
1665: for (i = 0; rdp->rd_log[i]; i += j) {
1666: j = strcspn(rdp->rd_log + i, "\n");
1667: if (j == 0)
1668: buf_append(newbuf, line, line2 - line);
1669: else
1670: buf_append(newbuf, line, kw - 1 - line);
1671: if (rdp->rd_log[i + j])
1672: ++j;
1673: buf_append(newbuf, rdp->rd_log + i, j);
1674: }
1.88 ! joris 1675:
! 1676: if (i > 0 && rdp->rd_log[i - 1] != '\n')
! 1677: buf_putc(newbuf, '\n');
! 1678:
1.77 nicm 1679: buf_append(newbuf, line, line2 - line);
1680: for (j = 0; c + j < fin; ++j) {
1681: if (c[j] != ' ')
1682: break;
1.1 joris 1683: }
1.77 nicm 1684: if (c + j == fin || c[j] == '\n')
1685: c += j;
1.1 joris 1686: }
1687: }
1.18 niallo 1688:
1.77 nicm 1689: if (found) {
1690: buf_append(newbuf, c, fin - c);
1691: buf_free(bp);
1692: return (newbuf);
1693: } else {
1694: buf_free(newbuf);
1695: return (bp);
1696: }
1.1 joris 1697: }
1698:
1699: /*
1700: * rcs_deltatext_set()
1701: *
1702: * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1703: * Returns -1 on error, 0 on success.
1704: */
1705: int
1.12 niallo 1706: rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1.1 joris 1707: {
1708: size_t len;
1.12 niallo 1709: u_char *dtext;
1.1 joris 1710: struct rcs_delta *rdp;
1711:
1712: /* Write operations require full parsing */
1.68 tobias 1713: if (rcsparse_deltatexts(rfp, NULL))
1714: return (-1);
1.1 joris 1715:
1716: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1717: return (-1);
1718:
1.83 nicm 1719: free(rdp->rd_text);
1.1 joris 1720:
1.59 ray 1721: len = buf_len(bp);
1722: dtext = buf_release(bp);
1.12 niallo 1723: bp = NULL;
1.59 ray 1724:
1.1 joris 1725: if (len != 0) {
1.12 niallo 1726: rdp->rd_text = xmalloc(len);
1.1 joris 1727: rdp->rd_tlen = len;
1.12 niallo 1728: (void)memcpy(rdp->rd_text, dtext, len);
1.1 joris 1729: } else {
1730: rdp->rd_text = NULL;
1731: rdp->rd_tlen = 0;
1732: }
1.70 tobias 1733:
1.83 nicm 1734: free(dtext);
1.1 joris 1735:
1736: return (0);
1737: }
1738:
1739: /*
1740: * rcs_rev_setlog()
1741: *
1.51 tobias 1742: * Sets the log message of revision <rev> to <logtext>.
1.1 joris 1743: */
1744: int
1745: rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1746: {
1747: struct rcs_delta *rdp;
1748:
1749: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1750: return (-1);
1751:
1.83 nicm 1752: free(rdp->rd_log);
1.1 joris 1753:
1754: rdp->rd_log = xstrdup(logtext);
1755: rfp->rf_flags &= ~RCS_SYNCED;
1756: return (0);
1757: }
1758: /*
1759: * rcs_rev_getdate()
1760: *
1761: * Get the date corresponding to a given revision.
1762: * Returns the date on success, -1 on failure.
1763: */
1764: time_t
1765: rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1766: {
1767: struct rcs_delta *rdp;
1768:
1769: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1770: return (-1);
1771:
1772: return (mktime(&rdp->rd_date));
1773: }
1774:
1775: /*
1776: * rcs_state_set()
1777: *
1778: * Sets the state of revision <rev> to <state>
1779: * NOTE: default state is 'Exp'. States may not contain spaces.
1780: *
1781: * Returns -1 on failure, 0 on success.
1782: */
1783: int
1784: rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1785: {
1786: struct rcs_delta *rdp;
1787:
1788: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1789: return (-1);
1790:
1.83 nicm 1791: free(rdp->rd_state);
1.1 joris 1792:
1793: rdp->rd_state = xstrdup(state);
1794:
1795: rfp->rf_flags &= ~RCS_SYNCED;
1796:
1797: return (0);
1798: }
1799:
1800: /*
1801: * rcs_state_check()
1802: *
1803: * Check if string <state> is valid.
1804: *
1805: * Returns 0 if the string is valid, -1 otherwise.
1806: */
1807: int
1808: rcs_state_check(const char *state)
1809: {
1.66 tobias 1810: int ret;
1.80 deraadt 1811: const unsigned char *cp;
1.66 tobias 1812:
1813: ret = 0;
1814: cp = state;
1815: if (!isalpha(*cp++))
1.1 joris 1816: return (-1);
1817:
1.66 tobias 1818: for (; *cp != '\0'; cp++)
1819: if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1820: ret = -1;
1821: break;
1822: }
1823:
1824: return (ret);
1.1 joris 1825: }
1826:
1827: /*
1828: * rcs_kwexp_buf()
1829: *
1830: * Do keyword expansion on a buffer if necessary
1831: *
1832: */
1833: BUF *
1834: rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
1835: {
1836: struct rcs_delta *rdp;
1837: int expmode;
1838:
1839: /*
1840: * Do keyword expansion if required.
1841: */
1.71 tobias 1842: expmode = rcs_kwexp_get(rf);
1.1 joris 1843:
1844: if (!(expmode & RCS_KWEXP_NONE)) {
1845: if ((rdp = rcs_findrev(rf, rev)) == NULL)
1.5 ray 1846: errx(1, "could not fetch revision");
1.12 niallo 1847: return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1.1 joris 1848: }
1849: return (bp);
1850: }