1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /* Copyright (c) 1988 AT&T */
22 /* All Rights Reserved */
23 /*
24  * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
25  * Use is subject to license terms.
26  */
27 /*
28  * Copyright 2006-2020 J. Schilling
29  *
30  * @(#)vc.c	1.14 20/05/08 J. Schilling
31  */
32 #if defined(sun)
33 #pragma ident "@(#)vc.c 1.14 20/05/08 J. Schilling"
34 #endif
35 /*
36  * @(#)vc.c 1.6 06/12/12
37  */
38 
39 #if defined(sun)
40 #pragma ident	"@(#)vc.c"
41 #pragma ident	"@(#)sccs:cmd/vc.c"
42 #endif
43 # include	<defines.h>
44 
45 # define USD  1
46 # define DCL 2
47 # define ASG 4
48 
49 # define EQ '='
50 # define NEQ '!'
51 # define GT '>'
52 # define LT '<'
53 # define DELIM " \t"
54 # define TRUE 1
55 # define FALSE 0
56 
57 static char	Ctlchar = ':';
58 
59 struct	symtab	{
60 	int	usage;
61 	char	*name;
62 	char	*value;
63 	int	lenval;
64 };
65 static struct	symtab	*Sym;
66 static int	symsize;
67 
68 
69 static char 	*sptr;
70 static int	Skiptabs;
71 static int	Repall;
72 
73 /*
74  * Delflag is used to indicate when text is to be skipped.  It is decre-
75  * mented whenever an if condition is false, or when an if occurs
76  * within a false if/end statement.  It is decremented whenever an end is
77  * encountered and the Delflag is greater than zero.  Whenever Delflag
78  * is greater than zero text is skipped.
79  */
80 
81 static int	Delflag;
82 
83 /*
84  * Ifcount keeps track of the number of ifs and ends.  Each time
85  * an if is encountered Ifcount is incremented and each time an end is
86  * encountered it is decremented.
87  */
88 
89 static int	Ifcount;
90 static int	Lineno;
91 
92 static char	*Repflag;
93 static char	*Linend;
94 static int	Silent;
95 
96 	int	main __PR((int argc, char **argv));
97 static void	asgfunc __PR((char *aptr));
98 static void	dclfunc __PR((char *dptr));
99 static void	errfunc __PR((char *eptr));
100 static void	endfunc __PR((void));
101 static void	msgfunc __PR((char *mptr));
102 static void	repfunc __PR((char *s));
103 static void	iffunc __PR((char *iptr));
104 static int	door __PR((void));
105 static int	doand __PR((void));
106 #undef	exp
107 #define	exp	vc_exp
108 static int	exp __PR((void));
109 static char *	getid __PR((char *gptr));
110 static int	numcomp __PR((char *id1, char *id2));
111 static void	numck __PR((char *nptr));
112 static char *	replace __PR((char *ptr));
113 static int	lookup __PR((char *lname));
114 static int	putin __PR((char *pname, char *pvalue));
115 static char *	findch __PR((char *astr, int match));
116 static char *	ecopy __PR((char *s1, char *s2));
117 static char *	findstr __PR((char *astr, char *pat));
118 static char *	fgetl __PR((char **linep, size_t *sizep, FILE *f));
119 
120 /*
121  * The main program reads a line of text and sends it to be processed
122  * if it is a version control statement. If it is a line of text and
123  * the Delflag is equal to zero, it is written to the standard output.
124  */
125 
126 int
main(argc,argv)127 main(argc, argv)
128 int argc;
129 char *argv[];
130 {
131 	register  char *lineptr, *p;
132 	register int i;
133 	char *line = NULL;
134 	size_t linesize = 0;
135 	extern int Fflags;
136 
137 #ifdef	SCHILY_BUILD
138 	save_args(argc, argv);
139 #endif
140 	sccs_setinsbase(INS_BASE);
141 
142 	Fflags = FTLCLN | FTLMSG | FTLEXIT;
143 #ifdef	SCCS_FATALHELP
144 	Fflags |= FTLFUNC;
145 	Ffunc = sccsfatalhelp;
146 #endif
147 	setsig();
148 	for(i = 1; i< argc; i++) {
149 		p = argv[i];
150 		if (p[0] == '-')
151 			switch (p[1]) {
152 			case 's':
153 				Silent = 1;
154 				break;
155 			case 't':
156 				Skiptabs = 1;
157 				break;
158 			case 'a':
159 				Repall = 1;
160 				break;
161 			case 'c':
162 				Ctlchar = p[2];
163 				break;
164 			}
165 		else {
166 			p[size(p) - 1] = '\n';
167 			asgfunc(p);
168 		}
169 	}
170 	while (fgetl(&line, &linesize, stdin) != NULL) {
171 		lineptr = line;
172 		Lineno++;
173 
174 		if (Repflag != 0) {
175 			ffree(Repflag);
176 			Repflag = 0;
177 		}
178 
179 		if (Skiptabs) {
180 			for (p = lineptr; *p; p++)
181 				if (*p == '\t')
182 					break;
183 			if (*p++ == '\t')
184 				lineptr = p;
185 		}
186 
187 		if (lineptr[0] != Ctlchar) {
188 			if (lineptr[0] == '\\' && lineptr[1] == Ctlchar)
189 				for (p = &lineptr[1];
190 				    (*lineptr++ = *p++) != '\0'; )
191 					;
192 			if(Delflag == 0) {
193 				if (Repall)
194 					repfunc(line);
195 				else
196 					if (fputs(line,stdout) == EOF) {
197 						FAILPUT;
198 					}
199 			}
200 			continue;
201 		}
202 
203 		lineptr++;
204 
205 		if (imatch("if ", lineptr))
206 			iffunc(&lineptr[3]);
207 		else if (imatch("end", lineptr))
208 			endfunc();
209 		else if (Delflag == 0) {
210 			if (imatch("asg ", lineptr))
211 				asgfunc(&lineptr[4]);
212 			else if (imatch("dcl ", lineptr))
213 				dclfunc(&lineptr[4]);
214 			else if (imatch("err", lineptr))
215 				errfunc(&lineptr[3]);
216 			else if (imatch("msg", lineptr))
217 				msgfunc(&lineptr[3]);
218 			else if (lineptr[0] == Ctlchar)
219 				repfunc(&lineptr[1]);
220 			else if (imatch("on", lineptr))
221 				Repall = 1;
222 			else if (imatch("off", lineptr))
223 				Repall = 0;
224 			else if (imatch("ctl ", lineptr))
225 				Ctlchar = lineptr[4];
226 			else {
227 				sprintf(SccsError,"unknown command on line %d (vc1)",
228 					Lineno);
229 				fatal(SccsError);
230 			}
231 		}
232 	}
233 	for(i = 0; i < symsize && Sym[i].usage != 0; i++) {
234 		if ((Sym[i].usage&USD) == 0 && !Silent)
235 			fprintf(stderr,"`%s' never used (vc2)\n",Sym[i].name);
236 		if ((Sym[i].usage&DCL) == 0 && !Silent)
237 			fprintf(stderr,"`%s' never declared (vc3)\n",
238 				Sym[i].name);
239 		if ((Sym[i].usage&ASG) == 0 && !Silent)
240 			fprintf(stderr,"`%s' never assigned a value (vc20)\n",
241 				Sym[i].name);
242 	}
243 	if (Ifcount > 0)
244 		fatal("`if' with no matching `end' (vc4)");
245 
246 	return (0);
247 }
248 
249 
250 /*
251  * Asgfunc accepts a pointer to a line picks up a keyword name, an
252  * equal sign and a value and calls putin to place it in the symbol table.
253  */
254 
255 static void
asgfunc(aptr)256 asgfunc(aptr)
257 register char *aptr;
258 {
259 	register char *end, *aname;
260 	char *avalue;
261 
262 	aptr = replace(aptr);
263 	NONBLANK(aptr);
264 	aname = aptr;
265 	end = Linend;
266 	aptr = findstr(aptr,"= \t");
267 	if (*aptr == ' ' || *aptr == '\t') {
268 		*aptr++ = '\0';
269 		aptr = findch(aptr,'=');
270 	}
271 	if (aptr == end) {
272 		sprintf(SccsError,"syntax on line %d (vc17)",Lineno);
273 		fatal(SccsError);
274 	}
275 	*aptr++ = '\0';
276 	avalue = getid(aptr);
277 	putin(aname, avalue);
278 }
279 
280 
281 /*
282  * Dclfunc accepts a pointer to a line and picks up keywords
283  * separated by commas.  It calls putin to put each keyword in the
284  * symbol table.  It returns when it sees a newline.
285  */
286 
287 static void
dclfunc(dptr)288 dclfunc(dptr)
289 register char *dptr;
290 {
291 	register char *end, *name;
292 	int i;
293 
294 	dptr = replace(dptr);
295 	end = Linend;
296 	NONBLANK(dptr);
297 	while (dptr < end) {
298 		name = dptr;
299 		dptr = findch(dptr,',');
300 		*dptr++ = '\0';
301 		if (Sym[i = lookup(name)].usage&DCL) {
302 			sprintf(SccsError,"`%s' declared twice on line %d (vc5)",
303 				name, Lineno);
304 			fatal(SccsError);
305 		}
306 		else
307 			Sym[i].usage |= DCL;
308 		NONBLANK(dptr);
309 	}
310 }
311 
312 
313 /*
314  * Errfunc calls fatal which stops the process.
315  */
316 
317 static void
errfunc(eptr)318 errfunc(eptr)
319 char *eptr;
320 {
321 	if (!Silent)
322 		fprintf(stderr,"ERROR:%s\n",replace(eptr));
323 	sprintf(SccsError,"err statement on line %d (vc15)", Lineno);
324 	fatal(SccsError);
325 }
326 
327 
328 /*
329  * Endfunc indicates an end has been found by decrementing the if count
330  * flag.  If because of a previous if statement, text was being skipped,
331  * Delflag is also decremented.
332  */
333 
334 static void
endfunc()335 endfunc()
336 {
337 	if (--Ifcount < 0) {
338 		sprintf(SccsError,"`end' without matching `if' on line %d (vc10)",
339 			Lineno);
340 		fatal(SccsError);
341 	}
342 	if (Delflag > 0)
343 		Delflag--;
344 	return;
345 }
346 
347 
348 /*
349  * Msgfunc accepts a pointer to a line and prints that line on the
350  * diagnostic output.
351  */
352 
353 static void
msgfunc(mptr)354 msgfunc(mptr)
355 char *mptr;
356 {
357 	if (!Silent)
358 		fprintf(stderr,"Message(%d):%s\n", Lineno, replace(mptr));
359 }
360 
361 
362 static void
repfunc(s)363 repfunc(s)
364 char *s;
365 {
366 	fprintf(stdout,"%s\n",replace(s));
367 }
368 
369 
370 /*
371  * Iffunc and the three functions following it, door, doand, and exp
372  * are responsible for parsing and interperting the condition in the
373  * if statement.  The BNF used is as follows:
374  *	<iffunc> ::=   [ "not" ] <door> EOL
375  *	<door> ::=     <doand> | <doand> "|" <door>
376  *	<doand>::=     <exp> | <exp> "&" <doand>
377  *	<exp>::=       "(" <door> ")" | <value> <operator> <value>
378  *	<operator>::=  "=" | "!=" | "<" | ">"
379  * And has precedence over or.  If the condition is false the Delflag
380  * is bumped to indicate that lines are to be skipped.
381  * An external variable, sptr is used for processing the line in
382  * iffunc, door, doand, exp, getid.
383  * Iffunc accepts a pointer to a line and sets sptr to that line.  The
384  * rest of iffunc, door, and doand follow the BNF exactly.
385  */
386 
387 static void
iffunc(iptr)388 iffunc(iptr)
389 char *iptr;
390 {
391 	register int value, not;
392 
393 	Ifcount++;
394 	if (Delflag > 0)
395 		Delflag++;
396 
397 	else {
398 		sptr = replace(iptr);
399 		NONBLANK(sptr);
400 		if (imatch("not ", sptr)) {
401 			not = FALSE;
402 			sptr += 4;
403 		}
404 		else not = TRUE;
405 
406 		value = door();
407 		if( *sptr != 0) {
408 			sprintf(SccsError,"syntax on line %d (vc18)",Lineno);
409 			fatal(SccsError);
410 		}
411 
412 		if (value != not)
413 			Delflag++;
414 	}
415 
416 	return;
417 }
418 
419 static int
door()420 door()
421 {
422 	int value;
423 	value = doand();
424 	NONBLANK(sptr);
425 	while (*sptr=='|') {
426 		sptr++;
427 		value |= doand();
428 		NONBLANK(sptr);
429 	}
430 	return(value);
431 }
432 
433 static int
doand()434 doand()
435 {
436 	int value;
437 	value = exp();
438 	NONBLANK(sptr);
439 	while (*sptr=='&') {
440 		sptr++;
441 		value &= exp();
442 		NONBLANK(sptr);
443 	}
444 	return(value);
445 }
446 
447 
448 /*
449  * After exp checks for parentheses, it picks up a value by calling getid,
450  * picks up an operator and calls getid to pick up the second value.
451  * Then based on the operator it calls either numcomp or equal to see
452  * if the exp is true or false and returns the correct value.
453  */
454 
455 static int
exp()456 exp()
457 {
458 	register char op, save;
459 	register int value = 0;
460 	char *id1, *id2, next = 0;
461 
462 	NONBLANK(sptr);
463 	if(*sptr == '(') {
464 		sptr++;
465 		value = door();
466 		NONBLANK(sptr);
467 		if (*sptr == ')') {
468 			sptr++;
469 			return(value);
470 		}
471 		else {
472 			sprintf(SccsError,"parenthesis error on line %d (vc11)",
473 				Lineno);
474 		}
475 	}
476 
477 	id1 = getid(sptr);
478 	if ((op = *sptr) != '\0')
479 		*sptr++ = '\0';
480 	if (op == NEQ && (next = *sptr++) == '\0')
481 		--sptr;
482 	id2 = getid(sptr);
483 	save = *sptr;
484 	*sptr = '\0';
485 
486 	if(op ==LT || op == GT) {
487 		value = numcomp(id1, id2);
488 		if ((op == GT && value == 1) || (op == LT && value == -1))
489 			value = TRUE;
490 		else value = FALSE;
491 	}
492 
493 	else if (op==EQ || (op==NEQ && next==EQ)) {
494 		value = equal(id1, id2);
495 		if ( op == NEQ)
496 			value = !value;
497 	}
498 
499 	else {
500 		sprintf(SccsError,"invalid operator on line %d (vc12)", Lineno);
501 		fatal(SccsError);
502 	}
503 	*sptr = save;
504 	return(value);
505 }
506 
507 
508 /*
509  * Getid picks up a value off a line and returns a pointer to the value.
510  */
511 
512 static char *
getid(gptr)513 getid(gptr)
514 register char *gptr;
515 {
516 	register char *id;
517 
518 	NONBLANK(gptr);
519 	id = gptr;
520 	gptr = findstr(gptr,DELIM);
521 	if (*gptr)
522 		*gptr++ = '\0';
523 	NONBLANK(gptr);
524 	sptr = gptr;
525 	return(id);
526 }
527 
528 
529 /*
530  * Numcomp accepts two pointers to strings of digits and calls numck
531  * to see if the strings contain only digits.  It returns -1 if
532  * the first is less than the second, 1 if the first is greater than the
533  * second and 0 if the two are equal.
534  */
535 
536 static int
numcomp(id1,id2)537 numcomp(id1, id2)
538 register char *id1, *id2;
539 {
540 	int k1, k2;
541 
542 	numck(id1);
543 	numck(id2);
544 	while (*id1 == '0')
545 		id1++;
546 	while (*id2 == '0')
547 		id2++;
548 	if ((k1 = size(id1)) > (k2 = size(id2)))
549 		return(1);
550 	else if (k1 < k2)
551 		return(-1);
552 	else while(*id1 != '\0') {
553 		if(*id1 > *id2)
554 			return(1);
555 		else if(*id1 < *id2)
556 			return(-1);
557 		id1++;
558 		id2++;
559 	}
560 	return(0);
561 }
562 
563 
564 /*
565  * Numck accepts a pointer to a string and checks to see if they are
566  * all digits.  If they're not it calls fatal, otherwise it returns.
567  */
568 
569 static void
numck(nptr)570 numck(nptr)
571 register char *nptr;
572 {
573 	for (; *nptr != '\0'; nptr++)
574 		if (!numeric(*nptr)) {
575 			sprintf(SccsError,"non-numerical value on line %d (vc14)",
576 				Lineno);
577 			fatal(SccsError);
578 		}
579 	return;
580 }
581 
582 
583 /*
584  * Replace accepts a pointer to a line and scans the line for a keyword
585  * enclosed in control characters.  If it doesn't find one it returns
586  * a pointer to the begining of the line.  Otherwise, it calls
587  * lookup to find the keyword.
588  * It rewrites the line substituting the value for the
589  * keyword enclosed in control characters.  It then continues scanning
590  * the line until no control characters are found and returns a pointer to
591  * the begining of the new line.
592  */
593 
594 # define INCR(int) if (++int == nslots) { \
595 		nslots += 32; \
596 		if ((slots = realloc(slots, nslots * sizeof (slots[0]))) == NULL) { \
597 			sprintf(SccsError,"out of space [line %d] (vc16)",Lineno); \
598 			fatal(SccsError); } \
599 		}
600 
601 static char *
replace(ptr)602 replace(ptr)
603 char *ptr;
604 {
605 	char **slots = NULL;
606 	int nslots = 0;
607 	int i,j,newlen;
608 	register char *s, *t, *p;
609 
610 	for (s=ptr; *s++!='\n';);
611 	*(--s) = '\0';
612 	Linend = s;
613 	i = -1;
614 	for (p=ptr; *(s=findch(p,Ctlchar)); p=t) {
615 		*s++ = '\0';
616 		INCR(i);
617 		slots[i] = p;
618 		if (*(t=findch(s,Ctlchar))==0) {
619 			sprintf(SccsError,"unmatched `%c' on line %d (vc7)",
620 				Ctlchar,Lineno);
621 			fatal(SccsError);
622 		}
623 		*t++ = '\0';
624 		INCR(i);
625 		slots[i] = Sym[j = lookup(s)].value;
626 		Sym[j].usage |= USD;
627 	}
628 	INCR(i);
629 	slots[i] = p;
630 	if (i == 0) {
631 		free(slots);
632 		return (ptr);
633 	}
634 	newlen = 0;
635 	for (j=0; j<=i; j++)
636 		newlen += (size(slots[j])-1);
637 	t = Repflag = fmalloc((unsigned int) ++newlen);
638 	for (j=0; j<=i; j++)
639 		t = ecopy(slots[j],t);
640 	Linend = t;
641 	free(slots);
642 	return(Repflag);
643 }
644 
645 
646 /*
647  * Lookup accepts a pointer to a keyword name and searches the symbol
648  * table for the keyword.  It returns its index in the table if its there,
649  * otherwise it puts the keyword in the table.
650  */
651 
652 static int
lookup(lname)653 lookup(lname)
654 char *lname;
655 {
656 	register int i;
657 	register char *t;
658 	register struct symtab *s;
659 
660 	t = lname;
661 	while (((i = *t++) != 0) &&
662 		((i>='A' && i<='Z') || (i>='a' && i<='z') ||
663 			(i!= *lname && i>='0' && i<='9')));
664 	if (i) {
665 		sprintf(SccsError,"invalid keyword name on line %d (vc9)",Lineno);
666 		fatal(SccsError);
667 	}
668 
669 	for (i = 0; i < symsize && Sym[i].usage != 0; i++)
670 		if (equal(lname, Sym[i].name)) return(i);
671 	if (i >= symsize) {
672 		symsize += 64;
673 		Sym = realloc(Sym, symsize * sizeof (Sym[0]));
674 		if (Sym == NULL) {
675 			fatal("out of space (vc6)");
676 			/*NOTREACHED*/
677 		}
678 		memset(&Sym[i], '\0', (symsize-i) * sizeof (Sym[0]));
679 	}
680 	s = &Sym[i];
681 	copy(lname, (s->name = fmalloc(size(lname))));
682 	copy("",(s->value = fmalloc((unsigned int) (s->lenval = 1))));
683 	return(i);
684 }
685 
686 
687 /*
688  * Putin accepts a pointer to a keyword name, and a pointer to a value.
689  * It puts this information in the symbol table by calling lookup.
690  * It returns the index of the name in the table.
691  */
692 
693 static int
putin(pname,pvalue)694 putin(pname, pvalue)
695 char *pname;
696 char *pvalue;
697 {
698 	register int i;
699 	register struct symtab *s;
700 
701 	s = &Sym[i = lookup(pname)];
702 	ffree(s->value);
703 	s->lenval = size(pvalue);
704 	copy(pvalue, (s->value = fmalloc((unsigned int) (s->lenval))));
705 	s->usage |= ASG;
706 	return(i);
707 }
708 
709 static char *
findch(astr,match)710 findch(astr,match)
711 char *astr, match;
712 {
713 	register char *s, *t, c;
714 	char *temp;
715 
716 	for (s=astr; ((c = *s) != '\0') && c!=match; s++)
717 		if (c=='\\') {
718 			if (s[1]==0) {
719 				sprintf(SccsError,"syntax on line %d (vc19)",Lineno);
720 				fatal(SccsError);
721 			}
722 			else {
723 				for (t = (temp=s) + 1; (*s++ = *t++) != '\0';);
724 				s = temp;
725 			}
726 		}
727 	return(s);
728 }
729 
730 
731 static char *
ecopy(s1,s2)732 ecopy(s1,s2)
733 char *s1, *s2;
734 {
735 	register char *r1, *r2;
736 
737 	r1 = s1;
738 	r2 = s2;
739 	while ((*r2++ = *r1++) != '\0');
740 	return(--r2);
741 }
742 
743 
744 static char *
findstr(astr,pat)745 findstr(astr,pat)
746 char *astr, *pat;
747 {
748 	register char *s, *t, c;
749 	char *temp;
750 
751 	for (s=astr; ((c = *s) != '\0') && any(c,pat)==0; s++)
752 		if (c=='\\') {
753 			if (s[1]==0) {
754 				sprintf(SccsError,"syntax on line %d (vc19)",Lineno);
755 				fatal(SccsError);
756 			}
757 			else {
758 				for (t = (temp=s) + 1; (*s++ = *t++) != '\0';);
759 				s = temp;
760 			}
761 		}
762 	return(s);
763 }
764 
765 #define	DEF_LINE_SIZE	128
766 
767 static char *
fgetl(linep,sizep,f)768 fgetl(linep, sizep, f)
769 	char	**linep;
770 	size_t	*sizep;
771 	FILE	*f;
772 {
773 	int	eof;
774 	register size_t used = 0;
775 	register size_t line_size;
776 	register char	*line;
777 
778 	line_size = *sizep;
779 	line = *linep;
780 	if (line_size == 0) {
781 		line_size = DEF_LINE_SIZE;
782 		line = (char *) malloc(line_size);
783 		if (line == NULL)
784 			fatal(gettext("line too long (vc21)"));
785 	}
786 	/* read until EOF or newline encountered */
787 	line[0] = '\0';
788 	do {
789 		line[line_size - 1] = '\t';	/* arbitrary non-zero char */
790 		line[line_size - 2] = ' ';	/* arbitrary non-newline char */
791 		if (!(eof = (fgets(line+used,
792 				    line_size-used,
793 				    f) == NULL))) {
794 
795 			if (line[line_size - 1] != '\0' ||
796 			    line[line_size - 2] == '\n')
797 				break;
798 
799 			used = line_size - 1;
800 
801 			line_size += DEF_LINE_SIZE;
802 			line = (char *) realloc(line, line_size);
803 			if (line == NULL)
804 				fatal(gettext("line too long (vc21)"));
805 		}
806 	} while (!eof);
807 	used += strlen(&line[used]);
808 	*linep = line;
809 	*sizep = line_size;
810 	if (eof && (used == 0))
811 		return (NULL);
812 	return (line);
813 }
814