1/*	$NetBSD: rcskeep.c,v 1.6 1996/10/15 07:00:21 veego Exp $	*/
2
3/* Extract RCS keyword string values from working files.  */
4
5/* Copyright 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7   Distributed under license by the Free Software Foundation, Inc.
8
9This file is part of RCS.
10
11RCS is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2, or (at your option)
14any later version.
15
16RCS is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with RCS; see the file COPYING.
23If not, write to the Free Software Foundation,
2459 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26Report problems and direct all questions to:
27
28    rcs-bugs@cs.purdue.edu
29
30*/
31
32/*
33 * $Log: rcskeep.c,v $
34 * Revision 1.7  1996/10/21 07:00:08  veego
35 * Fix missing "#ifdef LOCALID" from pr#2876
36 *
37 * Revision 1.6  1996/10/15 07:00:21  veego
38 * Merge rcs 5.7.
39 *
40 * Revision 5.10  1995/06/16 06:19:24  eggert
41 * Update FSF address.
42 *
43 * Revision 5.9  1995/06/01 16:23:43  eggert
44 * (getoldkeys): Don't panic if a Name: is empty.
45 *
46 * Revision 5.8  1994/03/17 14:05:48  eggert
47 * Remove lint.
48 *
49 * Revision 5.7  1993/11/09 17:40:15  eggert
50 * Use simpler timezone parsing strategy now that we're using ISO 8601 format.
51 *
52 * Revision 5.6  1993/11/03 17:42:27  eggert
53 * Scan for Name keyword.  Improve quality of diagnostics.
54 *
55 * Revision 5.5  1992/07/28  16:12:44  eggert
56 * Statement macro names now end in _.
57 *
58 * Revision 5.4  1991/08/19  03:13:55  eggert
59 * Tune.
60 *
61 * Revision 5.3  1991/04/21  11:58:25  eggert
62 * Shorten names to keep them distinct on shortname hosts.
63 *
64 * Revision 5.2  1990/10/04  06:30:20  eggert
65 * Parse time zone offsets; future RCS versions may output them.
66 *
67 * Revision 5.1  1990/09/20  02:38:56  eggert
68 * ci -k now checks dates more thoroughly.
69 *
70 * Revision 5.0  1990/08/22  08:12:53  eggert
71 * Retrieve old log message if there is one.
72 * Don't require final newline.
73 * Remove compile-time limits; use malloc instead.  Tune.
74 * Permit dates past 1999/12/31.  Ansify and Posixate.
75 *
76 * Revision 4.6  89/05/01  15:12:56  narten
77 * changed copyright header to reflect current distribution rules
78 *
79 * Revision 4.5  88/08/09  19:13:03  eggert
80 * Remove lint and speed up by making FILE *fp local, not global.
81 *
82 * Revision 4.4  87/12/18  11:44:21  narten
83 * more lint cleanups (Guy Harris)
84 *
85 * Revision 4.3  87/10/18  10:35:50  narten
86 * Updating version numbers. Changes relative to 1.1 actually relative
87 * to 4.1
88 *
89 * Revision 1.3  87/09/24  14:00:00  narten
90 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
91 * warnings)
92 *
93 * Revision 1.2  87/03/27  14:22:29  jenkins
94 * Port to suns
95 *
96 * Revision 4.1  83/05/10  16:26:44  wft
97 * Added new markers Id and RCSfile; extraction added.
98 * Marker matching with trymatch().
99 *
100 * Revision 3.2  82/12/24  12:08:26  wft
101 * added missing #endif.
102 *
103 * Revision 3.1  82/12/04  13:22:41  wft
104 * Initial revision.
105 *
106 */
107
108#include  "rcsbase.h"
109
110libId(keepId, "Id: rcskeep.c,v 5.10 1995/06/16 06:19:24 eggert Exp")
111
112static int badly_terminated P((void));
113static int checknum P((char const*));
114static int get0val P((int,RILE*,struct buf*,int));
115static int getval P((RILE*,struct buf*,int));
116static int keepdate P((RILE*));
117static int keepid P((int,RILE*,struct buf*));
118static int keeprev P((RILE*));
119
120int prevkeys;
121struct buf prevauthor, prevdate, prevname, prevrev, prevstate;
122
123	int
124getoldkeys(fp)
125	register RILE *fp;
126/* Function: Tries to read keyword values for author, date,
127 * revision number, and state out of the file fp.
128 * If fp is null, workname is opened and closed instead of using fp.
129 * The results are placed into
130 * prevauthor, prevdate, prevname, prevrev, prevstate.
131 * Aborts immediately if it finds an error and returns false.
132 * If it returns true, it doesn't mean that any of the
133 * values were found; instead, check to see whether the corresponding arrays
134 * contain the empty string.
135 */
136{
137    register int c;
138    char keyword[keylength+1];
139    register char * tp;
140    int needs_closing;
141    int prevname_found;
142
143    if (prevkeys)
144	return true;
145
146    needs_closing = false;
147    if (!fp) {
148	if (!(fp = Iopen(workname, FOPEN_R_WORK, (struct stat*)0))) {
149	    eerror(workname);
150	    return false;
151	}
152	needs_closing = true;
153    }
154
155    /* initialize to empty */
156    bufscpy(&prevauthor, "");
157    bufscpy(&prevdate, "");
158    bufscpy(&prevname, "");  prevname_found = 0;
159    bufscpy(&prevrev, "");
160    bufscpy(&prevstate, "");
161
162    c = '\0'; /* anything but KDELIM */
163    for (;;) {
164        if ( c==KDELIM) {
165	    do {
166		/* try to get keyword */
167		tp = keyword;
168		for (;;) {
169		    Igeteof_(fp, c, goto ok;)
170		    switch (c) {
171			default:
172			    if (keyword+keylength <= tp)
173				break;
174			    *tp++ = c;
175			    continue;
176
177			case '\n': case KDELIM: case VDELIM:
178			    break;
179		    }
180		    break;
181		}
182	    } while (c==KDELIM);
183            if (c!=VDELIM) continue;
184	    *tp = c;
185	    Igeteof_(fp, c, break;)
186	    switch (c) {
187		case ' ': case '\t': break;
188		default: continue;
189	    }
190
191	    switch (trymatch(keyword)) {
192            case Author:
193		if (!keepid(0, fp, &prevauthor))
194		    return false;
195		c = 0;
196                break;
197            case Date:
198		if (!(c = keepdate(fp)))
199		    return false;
200                break;
201            case Header:
202            case Id:
203#ifdef LOCALID
204	    case LocalId:
205#endif
206		if (!(
207		      getval(fp, (struct buf*)0, false) &&
208		      keeprev(fp) &&
209		      (c = keepdate(fp)) &&
210		      keepid(c, fp, &prevauthor) &&
211		      keepid(0, fp, &prevstate)
212		))
213		    return false;
214		/* Skip either ``who'' (new form) or ``Locker: who'' (old).  */
215		if (getval(fp, (struct buf*)0, true) &&
216		    getval(fp, (struct buf*)0, true))
217			c = 0;
218		else if (nerror)
219			return false;
220		else
221			c = KDELIM;
222		break;
223            case Locker:
224		(void) getval(fp, (struct buf*)0, false);
225		c = 0;
226		break;
227            case Log:
228            case RCSfile:
229            case Source:
230		if (!getval(fp, (struct buf*)0, false))
231		    return false;
232		c = 0;
233                break;
234	    case Name:
235		if (getval(fp, &prevname, false)) {
236		    if (*prevname.string)
237			checkssym(prevname.string);
238		    prevname_found = 1;
239		}
240		c = 0;
241		break;
242            case Revision:
243		if (!keeprev(fp))
244		    return false;
245		c = 0;
246                break;
247            case State:
248		if (!keepid(0, fp, &prevstate))
249		    return false;
250		c = 0;
251                break;
252            default:
253               continue;
254            }
255	    if (!c)
256		Igeteof_(fp, c, c=0;)
257	    if (c != KDELIM) {
258		workerror("closing %c missing on keyword", KDELIM);
259		return false;
260	    }
261	    if (prevname_found &&
262		*prevauthor.string && *prevdate.string &&
263		*prevrev.string && *prevstate.string
264	    )
265                break;
266        }
267	Igeteof_(fp, c, break;)
268    }
269
270 ok:
271    if (needs_closing)
272	Ifclose(fp);
273    else
274	Irewind(fp);
275    prevkeys = true;
276    return true;
277}
278
279	static int
280badly_terminated()
281{
282	workerror("badly terminated keyword value");
283	return false;
284}
285
286	static int
287getval(fp, target, optional)
288	register RILE *fp;
289	struct buf *target;
290	int optional;
291/* Reads a keyword value from FP into TARGET.
292 * Returns true if one is found, false otherwise.
293 * Does not modify target if it is 0.
294 * Do not report an error if OPTIONAL is set and KDELIM is found instead.
295 */
296{
297	int c;
298	Igeteof_(fp, c, return badly_terminated();)
299	return get0val(c, fp, target, optional);
300}
301
302	static int
303get0val(c, fp, target, optional)
304	register int c;
305	register RILE *fp;
306	struct buf *target;
307	int optional;
308/* Reads a keyword value from C+FP into TARGET, perhaps OPTIONALly.
309 * Same as getval, except C is the lookahead character.
310 */
311{   register char * tp;
312    char const *tlim;
313    register int got1;
314
315    if (target) {
316	bufalloc(target, 1);
317	tp = target->string;
318	tlim = tp + target->size;
319    } else
320	tlim = tp = 0;
321    got1 = false;
322    for (;;) {
323	switch (c) {
324	    default:
325		got1 = true;
326		if (tp) {
327		    *tp++ = c;
328		    if (tlim <= tp)
329			tp = bufenlarge(target, &tlim);
330		}
331		break;
332
333	    case ' ':
334	    case '\t':
335		if (tp) {
336		    *tp = 0;
337#		    ifdef KEEPTEST
338			VOID printf("getval: %s\n", target);
339#		    endif
340		}
341		return got1;
342
343	    case KDELIM:
344		if (!got1 && optional)
345		    return false;
346		/* fall into */
347	    case '\n':
348	    case 0:
349		return badly_terminated();
350	}
351	Igeteof_(fp, c, return badly_terminated();)
352    }
353}
354
355
356	static int
357keepdate(fp)
358	RILE *fp;
359/* Function: reads a date prevdate; checks format
360 * Return 0 on error, lookahead character otherwise.
361 */
362{
363    struct buf prevday, prevtime;
364    register int c;
365
366    c = 0;
367    bufautobegin(&prevday);
368    if (getval(fp,&prevday,false)) {
369	bufautobegin(&prevtime);
370	if (getval(fp,&prevtime,false)) {
371	    Igeteof_(fp, c, c=0;)
372	    if (c) {
373		register char const *d = prevday.string, *t = prevtime.string;
374		bufalloc(&prevdate, strlen(d) + strlen(t) + 9);
375		VOID sprintf(prevdate.string, "%s%s %s%s",
376		    /* Parse dates put out by old versions of RCS.  */
377		      isdigit(d[0]) && isdigit(d[1]) && !isdigit(d[2])
378		    ? "19" : "",
379		    d, t,
380		    strchr(t,'-') || strchr(t,'+')  ?  ""  :  "+0000"
381		);
382	    }
383	}
384	bufautoend(&prevtime);
385    }
386    bufautoend(&prevday);
387    return c;
388}
389
390	static int
391keepid(c, fp, b)
392	int c;
393	RILE *fp;
394	struct buf *b;
395/* Get previous identifier from C+FP into B.  */
396{
397	if (!c)
398	    Igeteof_(fp, c, return false;)
399	if (!get0val(c, fp, b, false))
400	    return false;
401	checksid(b->string);
402	return !nerror;
403}
404
405	static int
406keeprev(fp)
407	RILE *fp;
408/* Get previous revision from FP into prevrev.  */
409{
410	return getval(fp,&prevrev,false) && checknum(prevrev.string);
411}
412
413
414	static int
415checknum(s)
416	char const *s;
417{
418    register char const *sp;
419    register int dotcount = 0;
420    for (sp=s; ; sp++) {
421	switch (*sp) {
422	    case 0:
423		if (dotcount & 1)
424		    return true;
425		else
426		    break;
427
428	    case '.':
429		dotcount++;
430		continue;
431
432	    default:
433		if (isdigit(*sp))
434		    continue;
435		break;
436	}
437	break;
438    }
439    workerror("%s is not a revision number", s);
440    return false;
441}
442
443
444
445#ifdef KEEPTEST
446
447/* Print the keyword values found.  */
448
449char const cmdid[] ="keeptest";
450
451	int
452main(argc, argv)
453int  argc; char  *argv[];
454{
455        while (*(++argv)) {
456		workname = *argv;
457		getoldkeys((RILE*)0);
458                VOID printf("%s:  revision: %s, date: %s, author: %s, name: %s, state: %s\n",
459			    *argv, prevrev.string, prevdate.string, prevauthor.string, prevname.string, prevstate.string);
460	}
461	exitmain(EXIT_SUCCESS);
462}
463#endif
464