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