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