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
7 This file is part of RCS.
8
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Report 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
101 libId(keepId, "$FreeBSD$")
102
103 static int badly_terminated P((void));
104 static int checknum P((char const*));
105 static int get0val P((int,RILE*,struct buf*,int));
106 static int getval P((RILE*,struct buf*,int));
107 static int keepdate P((RILE*));
108 static int keepid P((int,RILE*,struct buf*));
109 static int keeprev P((RILE*));
110
111 int prevkeys;
112 struct buf prevauthor, prevdate, prevname, prevrev, prevstate;
113
114 int
getoldkeys(fp)115 getoldkeys(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
badly_terminated()269 badly_terminated()
270 {
271 workerror("badly terminated keyword value");
272 return false;
273 }
274
275 static int
getval(fp,target,optional)276 getval(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
get0val(c,fp,target,optional)292 get0val(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
keepdate(fp)346 keepdate(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
keepid(c,fp,b)380 keepid(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
keeprev(fp)395 keeprev(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
checknum(s)404 checknum(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
438 char const cmdid[] ="keeptest";
439
440 int
main(argc,argv)441 main(argc, argv)
442 int 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