rcskeep.c revision 11894
193787Sdes/* Extract RCS keyword string values from working files.  */
293787Sdes
393787Sdes/* Copyright 1982, 1988, 1989 Walter Tichy
493787Sdes   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
593787Sdes   Distributed under license by the Free Software Foundation, Inc.
693787Sdes
793787SdesThis file is part of RCS.
893787Sdes
993787SdesRCS is free software; you can redistribute it and/or modify
1093787Sdesit under the terms of the GNU General Public License as published by
1193787Sdesthe Free Software Foundation; either version 2, or (at your option)
1293787Sdesany later version.
1393787Sdes
1494691SdesRCS is distributed in the hope that it will be useful,
1594691Sdesbut WITHOUT ANY WARRANTY; without even the implied warranty of
1694691SdesMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1794691SdesGNU General Public License for more details.
1894691Sdes
1993787SdesYou should have received a copy of the GNU General Public License
2093787Sdesalong with RCS; see the file COPYING.
2193787SdesIf not, write to the Free Software Foundation,
2293787Sdes59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2393787Sdes
2493787SdesReport problems and direct all questions to:
2593787Sdes
2693787Sdes    rcs-bugs@cs.purdue.edu
2793787Sdes
2893787Sdes*/
2993787Sdes
3093787Sdes/*
31 * $Log: rcskeep.c,v $
32 * Revision 5.10  1995/06/16 06:19:24  eggert
33 * Update FSF address.
34 *
35 * Revision 5.9  1995/06/01 16:23:43  eggert
36 * (getoldkeys): Don't panic if a Name: is empty.
37 *
38 * Revision 5.8  1994/03/17 14:05:48  eggert
39 * Remove lint.
40 *
41 * Revision 5.7  1993/11/09 17:40:15  eggert
42 * Use simpler timezone parsing strategy now that we're using ISO 8601 format.
43 *
44 * Revision 5.6  1993/11/03 17:42:27  eggert
45 * Scan for Name keyword.  Improve quality of diagnostics.
46 *
47 * Revision 5.5  1992/07/28  16:12:44  eggert
48 * Statement macro names now end in _.
49 *
50 * Revision 5.4  1991/08/19  03:13:55  eggert
51 * Tune.
52 *
53 * Revision 5.3  1991/04/21  11:58:25  eggert
54 * Shorten names to keep them distinct on shortname hosts.
55 *
56 * Revision 5.2  1990/10/04  06:30:20  eggert
57 * Parse time zone offsets; future RCS versions may output them.
58 *
59 * Revision 5.1  1990/09/20  02:38:56  eggert
60 * ci -k now checks dates more thoroughly.
61 *
62 * Revision 5.0  1990/08/22  08:12:53  eggert
63 * Retrieve old log message if there is one.
64 * Don't require final newline.
65 * Remove compile-time limits; use malloc instead.  Tune.
66 * Permit dates past 1999/12/31.  Ansify and Posixate.
67 *
68 * Revision 4.6  89/05/01  15:12:56  narten
69 * changed copyright header to reflect current distribution rules
70 *
71 * Revision 4.5  88/08/09  19:13:03  eggert
72 * Remove lint and speed up by making FILE *fp local, not global.
73 *
74 * Revision 4.4  87/12/18  11:44:21  narten
75 * more lint cleanups (Guy Harris)
76 *
77 * Revision 4.3  87/10/18  10:35:50  narten
78 * Updating version numbers. Changes relative to 1.1 actually relative
79 * to 4.1
80 *
81 * Revision 1.3  87/09/24  14:00:00  narten
82 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
83 * warnings)
84 *
85 * Revision 1.2  87/03/27  14:22:29  jenkins
86 * Port to suns
87 *
88 * Revision 4.1  83/05/10  16:26:44  wft
89 * Added new markers Id and RCSfile; extraction added.
90 * Marker matching with trymatch().
91 *
92 * Revision 3.2  82/12/24  12:08:26  wft
93 * added missing #endif.
94 *
95 * Revision 3.1  82/12/04  13:22:41  wft
96 * Initial revision.
97 *
98 */
99
100#include  "rcsbase.h"
101
102libId(keepId, "$Id: rcskeep.c,v 5.10 1995/06/16 06:19:24 eggert Exp $")
103
104static int badly_terminated P((void));
105static int checknum P((char const*));
106static int get0val P((int,RILE*,struct buf*,int));
107static int getval P((RILE*,struct buf*,int));
108static int keepdate P((RILE*));
109static int keepid P((int,RILE*,struct buf*));
110static int keeprev P((RILE*));
111
112int prevkeys;
113struct buf prevauthor, prevdate, prevname, prevrev, prevstate;
114
115	int
116getoldkeys(fp)
117	register RILE *fp;
118/* Function: Tries to read keyword values for author, date,
119 * revision number, and state out of the file fp.
120 * If fp is null, workname is opened and closed instead of using fp.
121 * The results are placed into
122 * prevauthor, prevdate, prevname, prevrev, prevstate.
123 * Aborts immediately if it finds an error and returns false.
124 * If it returns true, it doesn't mean that any of the
125 * values were found; instead, check to see whether the corresponding arrays
126 * contain the empty string.
127 */
128{
129    register int c;
130    char keyword[keylength+1];
131    register char * tp;
132    int needs_closing;
133    int prevname_found;
134
135    if (prevkeys)
136	return true;
137
138    needs_closing = false;
139    if (!fp) {
140	if (!(fp = Iopen(workname, FOPEN_R_WORK, (struct stat*)0))) {
141	    eerror(workname);
142	    return false;
143	}
144	needs_closing = true;
145    }
146
147    /* initialize to empty */
148    bufscpy(&prevauthor, "");
149    bufscpy(&prevdate, "");
150    bufscpy(&prevname, "");  prevname_found = 0;
151    bufscpy(&prevrev, "");
152    bufscpy(&prevstate, "");
153
154    c = '\0'; /* anything but KDELIM */
155    for (;;) {
156        if ( c==KDELIM) {
157	    do {
158		/* try to get keyword */
159		tp = keyword;
160		for (;;) {
161		    Igeteof_(fp, c, goto ok;)
162		    switch (c) {
163			default:
164			    if (keyword+keylength <= tp)
165				break;
166			    *tp++ = c;
167			    continue;
168
169			case '\n': case KDELIM: case VDELIM:
170			    break;
171		    }
172		    break;
173		}
174	    } while (c==KDELIM);
175            if (c!=VDELIM) continue;
176	    *tp = c;
177	    Igeteof_(fp, c, break;)
178	    switch (c) {
179		case ' ': case '\t': break;
180		default: continue;
181	    }
182
183	    switch (trymatch(keyword)) {
184            case Author:
185		if (!keepid(0, fp, &prevauthor))
186		    return false;
187		c = 0;
188                break;
189            case Date:
190		if (!(c = keepdate(fp)))
191		    return false;
192                break;
193            case Header:
194            case Id:
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