Annotation of src/usr.bin/rcs/rcsprog.c, Revision 1.71
1.71 ! ray 1: /* $OpenBSD: rcsprog.c,v 1.70 2006/03/12 01:20:38 joris Exp $ */
1.1 deraadt 2: /*
3: * Copyright (c) 2005 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.59 xsa 27: #include "includes.h"
1.1 deraadt 28:
1.9 joris 29: #include "rcsprog.h"
1.1 deraadt 30:
1.29 joris 31: #define RCS_CMD_MAXARG 128
1.42 xsa 32: #define RCS_DEFAULT_SUFFIX ",v/"
1.56 joris 33: #define RCSPROG_OPTSTRING "A:a:b::c:e::hik:Lm:Mn:N:qt::TUVx:z:"
34:
35: #define DESC_PROMPT "enter description, terminated with single '.' " \
36: "or end of file:\nNOTE: This is NOT the log message!" \
37: "\n>> "
1.29 joris 38:
1.1 deraadt 39: const char rcs_version[] = "OpenCVS RCS version 3.6";
1.12 joris 40: int verbose = 1;
1.34 joris 41: int pipeout = 0;
1.1 deraadt 42:
1.56 joris 43: #define RCS_NFLAG 1
44: #define RCS_TFLAG 2
45: static int rcsflags = 0;
46:
1.36 xsa 47: int rcs_optind;
1.28 joris 48: char *rcs_optarg;
1.42 xsa 49: char *rcs_suffixes;
1.36 xsa 50: char *rcs_tmpdir = RCS_TMPDIR_DEFAULT;
1.28 joris 51:
1.1 deraadt 52: struct rcs_prog {
1.26 deraadt 53: char *prog_name;
54: int (*prog_hdlr)(int, char **);
55: void (*prog_usage)(void);
1.1 deraadt 56: } programs[] = {
1.2 deraadt 57: { "rcs", rcs_main, rcs_usage },
1.17 joris 58: { "ci", checkin_main, checkin_usage },
1.11 joris 59: { "co", checkout_main, checkout_usage },
1.20 joris 60: { "rcsclean", rcsclean_main, rcsclean_usage },
1.18 joris 61: { "rcsdiff", rcsdiff_main, rcsdiff_usage },
1.33 xsa 62: { "rcsmerge", rcsmerge_main, rcsmerge_usage },
1.21 joris 63: { "rlog", rlog_main, rlog_usage },
1.22 joris 64: { "ident", ident_main, ident_usage },
1.1 deraadt 65: };
1.31 joris 66:
1.68 joris 67: struct cvs_wklhead rcs_temp_files;
68:
69: void sighdlr(int);
1.56 joris 70: static void rcs_set_description(RCSFILE *, const char *);
71: static void rcs_attach_symbol(RCSFILE *, const char *);
72:
1.31 joris 73: void
1.68 joris 74: sighdlr(int sig)
75: {
76: cvs_worklist_clean(&rcs_temp_files, cvs_worklist_unlink);
77: _exit(1);
78: }
79:
80: void
1.31 joris 81: rcs_set_rev(const char *str, RCSNUM **rev)
82: {
1.32 joris 83: if (str == NULL)
84: return;
85:
1.52 joris 86: if ((*rev != NULL) && (*rev != RCS_HEAD_REV))
1.31 joris 87: cvs_log(LP_WARN, "redefinition of revision number");
88:
1.55 xsa 89: if ((*rev = rcsnum_parse(str)) == NULL)
90: fatal("bad revision number '%s'", str);
1.47 xsa 91: }
92:
93: /*
94: * rcs_get_mtime()
95: *
96: * Get <filename> last modified time.
97: * Returns last modified time on success, or -1 on failure.
98: */
99: time_t
100: rcs_get_mtime(const char *filename)
101: {
102: struct stat st;
103: time_t mtime;
104:
105: if (stat(filename, &st) == -1) {
106: cvs_log(LP_ERRNO, "failed to stat `%s'", filename);
107: return (-1);
108: }
109: mtime = (time_t)st.st_mtimespec.tv_sec;
110:
111: return (mtime);
112: }
113:
114: /*
115: * rcs_set_mtime()
116: *
117: * Set <filename> last modified time to <mtime> if it's not set to -1.
118: * Returns 0 on success, or -1 on failure.
119: */
120: int
121: rcs_set_mtime(const char *filename, time_t mtime)
122: {
123: static struct timeval tv[2];
124:
125: if (mtime == -1)
126: return (0);
127:
128: tv[0].tv_sec = mtime;
129: tv[1].tv_sec = tv[0].tv_sec;
130:
131: if (utimes(filename, tv) == -1) {
132: cvs_log(LP_ERRNO, "error setting utimes");
133: return (-1);
134: }
135:
136: return (0);
1.31 joris 137: }
1.1 deraadt 138:
1.9 joris 139: int
1.29 joris 140: rcs_init(char *envstr, char **argv, int argvlen)
141: {
142: u_int i;
143: int argc, error;
144: char linebuf[256], *lp, *cp;
145:
146: strlcpy(linebuf, envstr, sizeof(linebuf));
147: memset(argv, 0, argvlen * sizeof(char *));
148:
149: error = argc = 0;
150: for (lp = linebuf; lp != NULL;) {
1.61 xsa 151: cp = strsep(&lp, " \t\b\f\n\r\t\v");
1.29 joris 152: if (cp == NULL)
153: break;
154: else if (*cp == '\0')
155: continue;
156:
157: if (argc == argvlen) {
158: error++;
159: break;
160: }
161:
1.53 joris 162: argv[argc] = xstrdup(cp);
1.29 joris 163: argc++;
164: }
165:
166: if (error != 0) {
167: for (i = 0; i < (u_int)argc; i++)
1.53 joris 168: xfree(argv[i]);
1.29 joris 169: argc = -1;
170: }
171:
172: return (argc);
173: }
174:
175: int
1.28 joris 176: rcs_getopt(int argc, char **argv, const char *optstr)
177: {
178: char *a;
179: const char *c;
180: static int i = 1;
181: int opt, hasargument, ret;
182:
183: hasargument = 0;
184: rcs_optarg = NULL;
185:
186: if (i >= argc)
187: return (-1);
188:
189: a = argv[i++];
190: if (*a++ != '-')
191: return (-1);
192:
193: ret = 0;
194: opt = *a;
195: for (c = optstr; *c != '\0'; c++) {
196: if (*c == opt) {
197: a++;
198: ret = opt;
199:
200: if (*(c + 1) == ':') {
201: if (*(c + 2) == ':') {
202: if (*a != '\0')
203: hasargument = 1;
204: } else {
205: if (*a != '\0') {
206: hasargument = 1;
207: } else {
208: ret = 1;
209: break;
210: }
211: }
212: }
213:
214: if (hasargument == 1)
215: rcs_optarg = a;
216:
217: if (ret == opt)
218: rcs_optind++;
219: break;
220: }
221: }
222:
223: if (ret == 0)
224: cvs_log(LP_ERR, "unknown option -%c", opt);
225: else if (ret == 1)
226: cvs_log(LP_ERR, "missing argument for option -%c", opt);
227:
228: return (ret);
229: }
230:
231: int
1.9 joris 232: rcs_statfile(char *fname, char *out, size_t len)
233: {
1.62 xsa 234: int found, strdir;
1.42 xsa 235: char defaultsuffix[] = RCS_DEFAULT_SUFFIX;
1.9 joris 236: char filev[MAXPATHLEN], fpath[MAXPATHLEN];
1.42 xsa 237: char *ext, *slash;
1.9 joris 238: struct stat st;
239:
1.42 xsa 240: strdir = found = 0;
241:
242: /* we might have gotten the RCS/ dir in the argument string */
243: if (strstr(fname, RCSDIR) != NULL)
244: strdir = 1;
245:
246: if (rcs_suffixes != NULL)
247: ext = rcs_suffixes;
248: else
249: ext = defaultsuffix;
1.43 xsa 250:
1.42 xsa 251: for (;;) {
252: /*
253: * GNU documentation says -x,v/ specifies two suffixes,
254: * namely the ,v one and an empty one (which matches
255: * everything).
256: * The problem is that they don't follow this rule at
257: * all, and their documentation seems flawed.
258: * We try to be compatible, so let's do so.
259: */
260: if (*ext == '\0')
261: break;
262:
263: if ((slash = strchr(ext, '/')) != NULL)
264: *slash = '\0';
1.9 joris 265:
1.62 xsa 266: if (strlcpy(filev, fname, sizeof(filev)) >= sizeof(filev) ||
267: strlcat(filev, ext, sizeof(filev)) >= sizeof(filev))
1.55 xsa 268: fatal("rcs_statfile: path truncation");
1.42 xsa 269:
270: if ((strdir == 0) &&
271: (stat(RCSDIR, &st) != -1) && (st.st_mode & S_IFDIR)) {
1.62 xsa 272: if (strlcpy(fpath, RCSDIR,
273: sizeof(fpath)) >= sizeof(fpath) ||
274: strlcat(fpath, "/",
275: sizeof(fpath)) >= sizeof(fpath) ||
276: strlcat(fpath, filev,
277: sizeof(fpath)) >= sizeof(fpath))
1.55 xsa 278: fatal("rcs_statfile: path truncation");
1.42 xsa 279: } else {
1.62 xsa 280: if (strlcpy(fpath, filev,
281: sizeof(fpath)) >= sizeof(fpath))
1.57 xsa 282: fatal("rcs_statfile: path truncation");
1.42 xsa 283: }
284:
1.56 joris 285: if ((stat(fpath, &st) != -1) || (rcsflags & RCS_CREATE)) {
1.42 xsa 286: found++;
287: break;
288: }
289:
290: if (slash == NULL)
291: break;
292:
293: *slash++ = '/';
294: ext = slash;
1.9 joris 295: }
296:
1.42 xsa 297: if (found != 1) {
1.44 niallo 298: if ((strcmp(__progname, "rcsclean") != 0)
299: && (strcmp(__progname, "ci") != 0))
1.20 joris 300: cvs_log(LP_ERRNO, "%s", fpath);
1.9 joris 301: return (-1);
302: }
303:
1.62 xsa 304: if (strlcpy(out, fpath, len) >= len)
1.57 xsa 305: fatal("rcs_statfile: path truncation");
1.9 joris 306:
307: return (0);
308: }
1.1 deraadt 309:
310: int
311: main(int argc, char **argv)
312: {
313: u_int i;
1.29 joris 314: char *rcsinit, *cmd_argv[RCS_CMD_MAXARG];
315: int ret, cmd_argc;
1.1 deraadt 316:
317: ret = -1;
1.28 joris 318: rcs_optind = 1;
1.9 joris 319: cvs_log_init(LD_STD, 0);
1.68 joris 320: SLIST_INIT(&rcs_temp_files);
1.1 deraadt 321:
1.29 joris 322: cmd_argc = 0;
1.30 joris 323: cmd_argv[cmd_argc++] = argv[0];
1.29 joris 324: if ((rcsinit = getenv("RCSINIT")) != NULL) {
325: ret = rcs_init(rcsinit, cmd_argv + 1,
326: RCS_CMD_MAXARG - 1);
327: if (ret < 0) {
328: cvs_log(LP_ERRNO, "failed to prepend RCSINIT options");
329: exit (1);
330: }
331:
332: cmd_argc += ret;
333: }
1.36 xsa 334:
335: if ((rcs_tmpdir = getenv("TMPDIR")) == NULL)
336: rcs_tmpdir = RCS_TMPDIR_DEFAULT;
1.29 joris 337:
338: for (ret = 1; ret < argc; ret++)
339: cmd_argv[cmd_argc++] = argv[ret];
1.68 joris 340:
341: signal(SIGHUP, sighdlr);
342: signal(SIGINT, sighdlr);
343: signal(SIGQUIT, sighdlr);
344: signal(SIGABRT, sighdlr);
345: signal(SIGALRM, sighdlr);
346: signal(SIGTERM, sighdlr);
1.29 joris 347:
1.1 deraadt 348: for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++)
1.2 deraadt 349: if (strcmp(__progname, programs[i].prog_name) == 0) {
350: usage = programs[i].prog_usage;
1.29 joris 351: ret = programs[i].prog_hdlr(cmd_argc, cmd_argv);
1.2 deraadt 352: break;
353: }
1.1 deraadt 354:
1.23 niallo 355: exit(ret);
1.1 deraadt 356: }
357:
358:
359: void
360: rcs_usage(void)
361: {
362: fprintf(stderr,
1.66 jmc 363: "usage: rcs [-ehIiLMqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n"
1.67 xsa 364: " [-cstring] [-e[users]] [-kmode] [-mrev:msg]\n"
1.66 jmc 365: " [-orev] [-sstate[:rev]] [-tfile|str]\n"
1.67 xsa 366: " [-xsuffixes] file ...\n");
1.1 deraadt 367: }
368:
369: /*
370: * rcs_main()
371: *
372: * Handler for the `rcs' program.
373: * Returns 0 on success, or >0 on error.
374: */
375: int
376: rcs_main(int argc, char **argv)
377: {
378: int i, ch, flags, kflag, lkmode;
1.39 xsa 379: char fpath[MAXPATHLEN], ofpath[MAXPATHLEN];
1.56 joris 380: char *logstr, *logmsg, *nflag, *descfile;
1.39 xsa 381: char *alist, *comment, *elist, *unp, *sp;
1.1 deraadt 382: mode_t fmode;
1.39 xsa 383: RCSFILE *file, *oldfile;
1.24 joris 384: RCSNUM *logrev;
1.39 xsa 385: struct rcs_access *acp;
1.48 xsa 386: time_t rcs_mtime = -1;
1.1 deraadt 387:
388: kflag = lkmode = -1;
389: fmode = 0;
1.58 niallo 390: flags = RCS_RDWR|RCS_PARSE_FULLY;
1.56 joris 391: descfile = nflag = NULL;
1.39 xsa 392: logstr = alist = comment = elist = NULL;
1.1 deraadt 393:
1.51 xsa 394: while ((ch = rcs_getopt(argc, argv, RCSPROG_OPTSTRING)) != -1) {
1.1 deraadt 395: switch (ch) {
396: case 'A':
1.39 xsa 397: if (rcs_statfile(rcs_optarg, ofpath, sizeof(ofpath)) < 0)
398: exit(1);
1.56 joris 399: rcsflags |= CO_ACLAPPEND;
1.1 deraadt 400: break;
401: case 'a':
1.28 joris 402: alist = rcs_optarg;
1.1 deraadt 403: break;
404: case 'c':
1.28 joris 405: comment = rcs_optarg;
1.1 deraadt 406: break;
407: case 'e':
1.28 joris 408: elist = rcs_optarg;
1.1 deraadt 409: break;
410: case 'h':
1.2 deraadt 411: (usage)();
1.1 deraadt 412: exit(0);
413: case 'i':
414: flags |= RCS_CREATE;
1.56 joris 415: rcsflags |= RCS_CREATE;
1.1 deraadt 416: break;
417: case 'k':
1.28 joris 418: kflag = rcs_kflag_get(rcs_optarg);
1.1 deraadt 419: if (RCS_KWEXP_INVAL(kflag)) {
420: cvs_log(LP_ERR,
421: "invalid keyword substitution mode `%s'",
1.28 joris 422: rcs_optarg);
1.1 deraadt 423: exit(1);
424: }
425: break;
426: case 'L':
427: if (lkmode == RCS_LOCK_LOOSE)
428: cvs_log(LP_WARN, "-U overriden by -L");
429: lkmode = RCS_LOCK_STRICT;
430: break;
1.24 joris 431: case 'm':
1.53 joris 432: logstr = xstrdup(rcs_optarg);
1.24 joris 433: break;
1.1 deraadt 434: case 'M':
435: /* ignore for the moment */
436: break;
1.56 joris 437: case 'n':
438: nflag = xstrdup(rcs_optarg);
439: break;
440: case 'N':
441: nflag = xstrdup(rcs_optarg);
442: rcsflags |= RCS_NFLAG;
443: break;
1.12 joris 444: case 'q':
445: verbose = 0;
1.46 xsa 446: break;
1.56 joris 447: case 't':
448: descfile = rcs_optarg;
449: rcsflags |= RCS_TFLAG;
450: break;
1.46 xsa 451: case 'T':
1.56 joris 452: rcsflags |= PRESERVETIME;
1.12 joris 453: break;
1.1 deraadt 454: case 'U':
455: if (lkmode == RCS_LOCK_STRICT)
456: cvs_log(LP_WARN, "-L overriden by -U");
457: lkmode = RCS_LOCK_LOOSE;
458: break;
459: case 'V':
460: printf("%s\n", rcs_version);
461: exit(0);
1.45 xsa 462: case 'x':
463: rcs_suffixes = rcs_optarg;
1.51 xsa 464: break;
465: case 'z':
466: /*
467: * kept for compatibility
468: */
1.45 xsa 469: break;
1.1 deraadt 470: default:
1.2 deraadt 471: (usage)();
1.1 deraadt 472: exit(1);
473: }
474: }
475:
1.28 joris 476: argc -= rcs_optind;
477: argv += rcs_optind;
1.30 joris 478:
1.1 deraadt 479: if (argc == 0) {
480: cvs_log(LP_ERR, "no input file");
1.5 joris 481: (usage)();
1.1 deraadt 482: exit(1);
483: }
484:
485: for (i = 0; i < argc; i++) {
1.9 joris 486: if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0)
1.8 joris 487: continue;
1.6 joris 488:
1.35 niallo 489: if (verbose == 1)
490: printf("RCS file: %s\n", fpath);
1.48 xsa 491:
1.49 niallo 492: if ((file = rcs_open(fpath, flags, fmode)) == NULL)
1.6 joris 493: continue;
1.1 deraadt 494:
1.56 joris 495: if (rcsflags & RCS_CREATE)
496: rcs_set_description(file, NULL);
497:
498: if (rcsflags & RCS_TFLAG)
499: rcs_set_description(file, descfile);
500:
501: if (rcsflags & PRESERVETIME)
1.48 xsa 502: rcs_mtime = rcs_get_mtime(file->rf_path);
503:
1.56 joris 504: if (nflag != NULL)
505: rcs_attach_symbol(file, nflag);
506:
1.24 joris 507: if (logstr != NULL) {
508: if ((logmsg = strchr(logstr, ':')) == NULL) {
509: cvs_log(LP_ERR, "missing log message");
510: rcs_close(file);
511: continue;
512: }
513:
514: *logmsg++ = '\0';
515: if ((logrev = rcsnum_parse(logstr)) == NULL) {
1.48 xsa 516: cvs_log(LP_ERR,
517: "'%s' bad revision number", logstr);
1.24 joris 518: rcs_close(file);
519: continue;
520: }
521:
522: if (rcs_rev_setlog(file, logrev, logmsg) < 0) {
523: cvs_log(LP_ERR,
524: "failed to set logmsg for '%s' to '%s'",
525: logstr, logmsg);
526: rcs_close(file);
1.25 joris 527: rcsnum_free(logrev);
1.24 joris 528: continue;
529: }
530:
531: rcsnum_free(logrev);
1.39 xsa 532: }
533:
534: /* entries to add from <oldfile> */
1.56 joris 535: if (rcsflags & CO_ACLAPPEND) {
1.39 xsa 536: /* XXX */
537: if ((oldfile = rcs_open(ofpath, RCS_READ)) == NULL)
538: exit(1);
539:
540: TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list)
541: rcs_access_add(file, acp->ra_name);
542:
543: rcs_close(oldfile);
1.24 joris 544: }
545:
1.1 deraadt 546: /* entries to add to the access list */
547: if (alist != NULL) {
548: unp = alist;
549: do {
550: sp = strchr(unp, ',');
551: if (sp != NULL)
552: *(sp++) = '\0';
553:
554: rcs_access_add(file, unp);
555:
556: unp = sp;
557: } while (sp != NULL);
558: }
559:
560: if (comment != NULL)
561: rcs_comment_set(file, comment);
562:
563: if (kflag != -1)
564: rcs_kwexp_set(file, kflag);
565:
566: if (lkmode != -1)
567: rcs_lock_setmode(file, lkmode);
568:
569: rcs_close(file);
1.48 xsa 570:
1.56 joris 571: if (rcsflags & PRESERVETIME)
1.48 xsa 572: rcs_set_mtime(fpath, rcs_mtime);
1.9 joris 573:
1.14 xsa 574: if (verbose == 1)
1.12 joris 575: printf("done\n");
1.1 deraadt 576: }
1.24 joris 577:
578: if (logstr != NULL)
1.53 joris 579: xfree(logstr);
1.1 deraadt 580:
1.56 joris 581: if (nflag != NULL)
582: xfree(nflag);
583:
1.1 deraadt 584: return (0);
1.56 joris 585: }
586:
587: static void
588: rcs_attach_symbol(RCSFILE *file, const char *symname)
589: {
590: char *rnum;
591: RCSNUM *rev;
592: char rbuf[16];
593: int rm;
594:
595: rm = 0;
596: rev = NULL;
597: if ((rnum = strrchr(symname, ':')) != NULL) {
598: if (rnum[1] == '\0')
599: rev = file->rf_head;
600: *(rnum++) = '\0';
601: } else {
602: rm = 1;
603: }
604:
605: if (rev == NULL && rm != 1) {
606: if ((rev = rcsnum_parse(rnum)) == NULL)
607: fatal("bad revision %s", rnum);
608: }
609:
610: if (rcsflags & RCS_NFLAG)
611: rm = 1;
612:
613: if (rm == 1) {
614: if (rcs_sym_remove(file, symname) < 0) {
615: if ((rcs_errno == RCS_ERR_NOENT) &&
616: !(rcsflags & RCS_NFLAG))
617: cvs_log(LP_WARN,
618: "can't delete nonexisting symbol %s", symname);
619: } else {
620: if (rcsflags & RCS_NFLAG)
621: rm = 0;
622: }
623: }
624:
625: if (rm == 0) {
626: if ((rcs_sym_add(file, symname, rev) < 0) &&
627: (rcs_errno == RCS_ERR_DUPENT)) {
628: rcsnum_tostr(rcs_sym_getrev(file, symname),
629: rbuf, sizeof(rbuf));
630: fatal("symbolic name %s already bound to %s",
631: symname, rbuf);
632: }
633: }
634: }
635:
636: static void
637: rcs_set_description(RCSFILE *file, const char *in)
638: {
639: BUF *bp;
640: char *content, buf[128];
641:
642: content = NULL;
643: if (in != NULL) {
644: bp = cvs_buf_load(in, BUF_AUTOEXT);
645: } else {
646: bp = cvs_buf_alloc(64, BUF_AUTOEXT);
647:
648: printf(DESC_PROMPT);
649: for (;;) {
650: fgets(buf, sizeof(buf), stdin);
651: if (feof(stdin) || ferror(stdin) || buf[0] == '.')
652: break;
653: cvs_buf_append(bp, buf, strlen(buf));
654: printf(">> ");
655: }
656: }
657:
658: cvs_buf_putc(bp, '\0');
659: content = cvs_buf_release(bp);
660:
661: rcs_desc_set(file, content);
1.70 joris 662: xfree(content);
1.1 deraadt 663: }