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