rcskeep.c revision 11929
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, "$Id: rcskeep.c,v 1.3 1995/10/28 21:49:43 peter Exp $")
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		if (!(
195		      getval(fp, (struct buf*)0, false) &&
196		      keeprev(fp) &&
197		      (c = keepdate(fp)) &&
198		      keepid(c, fp, &prevauthor) &&
199		      keepid(0, fp, &prevstate)
200		))
201		    return false;
202		/* Skip either ``who'' (new form) or ``Locker: who'' (old).  */
203		if (getval(fp, (struct buf*)0, true) &&
204		    getval(fp, (struct buf*)0, true))
205			c = 0;
206		else if (nerror)
207			return false;
208		else
209			c = KDELIM;
210		break;
211            case Locker:
212		(void) getval(fp, (struct buf*)0, false);
213		c = 0;
214		break;
215            case Log:
216            case RCSfile:
217            case Source:
218		if (!getval(fp, (struct buf*)0, false))
219		    return false;
220		c = 0;
221                break;
222	    case Name:
223		if (getval(fp, &prevname, false)) {
224		    if (*prevname.string)
225			checkssym(prevname.string);
226		    prevname_found = 1;
227		}
228		c = 0;
229		break;
230            case Revision:
231		if (!keeprev(fp))
232		    return false;
233		c = 0;
234                break;
235            case State:
236		if (!keepid(0, fp, &prevstate))
237		    return false;
238		c = 0;
239                break;
240            default:
241               continue;
242            }
243	    if (!c)
244		Igeteof_(fp, c, c=0;)
245	    if (c != KDELIM) {
246		workerror("closing %c missing on keyword", KDELIM);
247		return false;
248	    }
249	    if (prevname_found &&
250		*prevauthor.string && *prevdate.string &&
251		*prevrev.string && *prevstate.string
252	    )
253                break;
254        }
255	Igeteof_(fp, c, break;)
256    }
257
258 ok:
259    if (needs_closing)
260	Ifclose(fp);
261    else
262	Irewind(fp);
263    prevkeys = true;
264    return true;
265}
266
267	static int
268badly_terminated()
269{
270	workerror("badly terminated keyword value");
271	return false;
272}
273
274	static int
275getval(fp, target, optional)
276	register RILE *fp;
277	struct buf *target;
278	int optional;
279/* Reads a keyword value from FP into TARGET.
280 * Returns true if one is found, false otherwise.
281 * Does not modify target if it is 0.
282 * Do not report an error if OPTIONAL is set and KDELIM is found instead.
283 */
284{
285	int c;
286	Igeteof_(fp, c, return badly_terminated();)
287	return get0val(c, fp, target, optional);
288}
289
290	static int
291get0val(c, fp, target, optional)
292	register int c;
293	register RILE *fp;
294	struct buf *target;
295	int optional;
296/* Reads a keyword value from C+FP into TARGET, perhaps OPTIONALly.
297 * Same as getval, except C is the lookahead character.
298 */
299{   register char * tp;
300    char const *tlim;
301    register int got1;
302
303    if (target) {
304	bufalloc(target, 1);
305	tp = target->string;
306	tlim = tp + target->size;
307    } else
308	tlim = tp = 0;
309    got1 = false;
310    for (;;) {
311	switch (c) {
312	    default:
313		got1 = true;
314		if (tp) {
315		    *tp++ = c;
316		    if (tlim <= tp)
317			tp = bufenlarge(target, &tlim);
318		}
319		break;
320
321	    case ' ':
322	    case '\t':
323		if (tp) {
324		    *tp = 0;
325#		    ifdef KEEPTEST
326			VOID printf("getval: %s\n", target);
327#		    endif
328		}
329		return got1;
330
331	    case KDELIM:
332		if (!got1 && optional)
333		    return false;
334		/* fall into */
335	    case '\n':
336	    case 0:
337		return badly_terminated();
338	}
339	Igeteof_(fp, c, return badly_terminated();)
340    }
341}
342
343
344	static int
345keepdate(fp)
346	RILE *fp;
347/* Function: reads a date prevdate; checks format
348 * Return 0 on error, lookahead character otherwise.
349 */
350{
351    struct buf prevday, prevtime;
352    register int c;
353
354    c = 0;
355    bufautobegin(&prevday);
356    if (getval(fp,&prevday,false)) {
357	bufautobegin(&prevtime);
358	if (getval(fp,&prevtime,false)) {
359	    Igeteof_(fp, c, c=0;)
360	    if (c) {
361		register char const *d = prevday.string, *t = prevtime.string;
362		bufalloc(&prevdate, strlen(d) + strlen(t) + 9);
363		VOID sprintf(prevdate.string, "%s%s %s%s",
364		    /* Parse dates put out by old versions of RCS.  */
365		      isdigit(d[0]) && isdigit(d[1]) && !isdigit(d[2])
366		    ? "19" : "",
367		    d, t,
368		    strchr(t,'-') || strchr(t,'+')  ?  ""  :  "+0000"
369		);
370	    }
371	}
372	bufautoend(&prevtime);
373    }
374    bufautoend(&prevday);
375    return c;
376}
377
378	static int
379keepid(c, fp, b)
380	int c;
381	RILE *fp;
382	struct buf *b;
383/* Get previous identifier from C+FP into B.  */
384{
385	if (!c)
386	    Igeteof_(fp, c, return false;)
387	if (!get0val(c, fp, b, false))
388	    return false;
389	checksid(b->string);
390	return !nerror;
391}
392
393	static int
394keeprev(fp)
395	RILE *fp;
396/* Get previous revision from FP into prevrev.  */
397{
398	return getval(fp,&prevrev,false) && checknum(prevrev.string);
399}
400
401
402	static int
403checknum(s)
404	char const *s;
405{
406    register char const *sp;
407    register int dotcount = 0;
408    for (sp=s; ; sp++) {
409	switch (*sp) {
410	    case 0:
411		if (dotcount & 1)
412		    return true;
413		else
414		    break;
415
416	    case '.':
417		dotcount++;
418		continue;
419
420	    default:
421		if (isdigit(*sp))
422		    continue;
423		break;
424	}
425	break;
426    }
427    workerror("%s is not a revision number", s);
428    return false;
429}
430
431
432
433#ifdef KEEPTEST
434
435/* Print the keyword values found.  */
436
437char const cmdid[] ="keeptest";
438
439	int
440main(argc, argv)
441int  argc; char  *argv[];
442{
443        while (*(++argv)) {
444		workname = *argv;
445		getoldkeys((RILE*)0);
446                VOID printf("%s:  revision: %s, date: %s, author: %s, name: %s, state: %s\n",
447			    *argv, prevrev.string, prevdate.string, prevauthor.string, prevname.string, prevstate.string);
448	}
449	exitmain(EXIT_SUCCESS);
450}
451#endif
452