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, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright (c) 1996, by Sun Microsystems, Inc.
24 * All rights reserved.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29/*
30 *	ticparse.c
31 *
32 *	Terminal Information Compiler
33 *
34 *	Copyright 1990, 1992 by Mortice Kern Systems Inc.  All rights reserved.
35 *
36 *	Portions of this code Copyright 1982 by Pavel Curtis.
37 */
38
39#ifdef M_RCSID
40#ifndef lint
41static char rcsID[] = "$Header: /rd/src/tic/rcs/ticparse.c 1.22 1995/06/27 14:56:46 ant Exp $";
42#endif
43#endif
44
45#include "tic.h"
46#include <ctype.h>
47#include <sys/stat.h>
48#include <errno.h>
49
50extern int get_token ANSI((void));	/* from ticscan.c */
51
52char *string_table;
53int next_free;		/* next free character in string_table */
54int table_size = 0; 	/* current string_table size */
55int term_names;		/* string table offset - current terminal */
56int part2 = 0;		/* set to allow old compiled defns to be used */
57int complete = 0;	/* 1 if entry done with no forward uses */
58
59struct use_item {
60	long	offset;
61	struct use_item	*fptr, *bptr;
62};
63
64struct use_header {
65	struct use_item	*head, *tail;
66};
67
68struct use_header	use_list = {NULL, NULL};
69int			use_count = 0;
70
71/*
72 *  The use_list is a doubly-linked list with NULLs terminating the lists:
73 *
74 *	   use_item    use_item    use_item
75 *	  ---------   ---------   ---------
76 *	  |       |   |       |   |       |   offset
77 *        |-------|   |-------|   |-------|
78 *	  |   ----+-->|   ----+-->|  NULL |   fptr
79 *	  |-------|   |-------|   |-------|
80 *	  |  NULL |<--+----   |<--+----   |   bptr
81 *	  ---------   ---------   ---------
82 *	  ^                       ^
83 *	  |  ------------------   |
84 *	  |  |       |        |   |
85 *	  +--+----   |    ----+---+
86 *	     |       |        |
87 *	     ------------------
88 *	       head     tail
89 *	          use_list
90 *
91 */
92
93char bad_start[] = m_textstr(
94	3107, "File does not start with terminal names in column one", "E"
95);
96char not_names[] = m_textstr(3108, "Token after a seek not NAMES", "E");
97char use_links[] = m_textstr(3109, "\
98\n\
99Error in following up use-links.  Either there is\n\
100a loop in the links or they reference non-existant\n\
101terminals.  The following is a list of the entries\n\
102involved:\n\n\
103", "E");
104char nomem_use_list[] = m_textstr(
105	3110, "Not enough memory for use_list element", "E"
106);
107char long_path[] = m_textstr(3111, "Pathname \"%c/%s\" too long.", "W char term");
108char more_than_one[] = m_textstr(
109	3112, "More than one entry defined for \"%s\".\n","W term"
110);
111char fail_open[] = m_textstr(3113, "Failed to open \"%s\".\n", "E filename");
112char write_err[] = m_textstr(3114, "Error in writing \"%s\".\n", "E filename");
113char synonym[] = m_textstr(3115, "Terminal \"%s\" is a synonym for itself.\n", "W term");
114char fail_link[] = m_textstr(3116, "Failed to link \"%s\" to \"%s\".\n", "E file1 file2");
115char name_check[] = m_textstr(3117, "\
116compile: Line %d: Illegal terminal name - '%s'\n\
117Terminal names must start with lowercase or digit.\n\
118", "E line_num term");
119char nomem[] = m_textstr(3118, "Failed to allocated memory.\n", "E");
120char unknown_term[] = m_textstr(202, "Unknown terminal \"%s\".\n", "E term");
121char no_terminfo[] = m_textstr(203, "No terminfo database.\n", "E");
122char unknown_cap[] = m_textstr(3119, "Unknown capability '%s'.", "E action");
123char unknown_token[] = m_textstr(3120, "Unknown token type.", "W");
124char wrong_type[] = m_textstr(3121, "Wrong type used for capability \"%s\".", "W type");
125
126
127/*f
128 * debugging routine to dump list
129 */
130STATIC int
131dump_list(str)
132char *str;
133{
134	struct use_item *ptr;
135	char line[512];
136
137	fprintf(stderr, "dump_list %s\n", str);
138	for (ptr = use_list.head; ptr != NULL; ptr = ptr->fptr)
139	{
140		fseek(stdin, ptr->offset, 0);
141		fgets(line, 1024, stdin);
142		fprintf(stderr, "ptr %x off %d bptr %x fptr %x str %s",
143		ptr, ptr->offset, ptr->bptr, ptr->fptr, line);
144	}
145	fprintf(stderr, "\n");
146}
147
148
149/*f
150 *	Generate an error message if given name does not begin with a
151 *	digit or lower-case letter.
152 */
153STATIC int
154check_name(name)
155char	*name;
156{
157	if (!isdigit(name[0]) && !isalpha(name[0])) {
158		fprintf(stderr, m_strmsg(name_check), curr_line, name);
159		exit(1);
160	}
161}
162
163/*f
164 *	Test whether this machine will need byte-swapping
165 */
166STATIC int
167must_swap()
168{
169	union {
170		short num;
171		char byte[2];
172	} test;
173	test.num = 1;
174	return (test.byte[1]);
175}
176
177
178/*f
179 *      Put a record of the given offset onto the use-list.
180 */
181STATIC int
182enqueue(offset)
183long	offset;
184{
185	struct use_item	*item;
186
187	item = (struct use_item *) malloc(sizeof(struct use_item));
188
189	if (item == NULL)
190	    syserr_abort(m_strmsg(nomem_use_list));
191
192	item->offset = offset;
193
194	if (use_list.head != NULL)
195	{
196	    item->bptr = use_list.tail;
197	    use_list.tail->fptr = item;
198	    item->fptr = NULL;
199	    use_list.tail = item;
200	}
201	else
202	{
203	    use_list.tail = use_list.head = item;
204	    item->fptr = item->bptr = NULL;
205	}
206
207	use_count ++;
208}
209
210
211
212/*f
213 *	remove the pointed-to item from the use_list
214 */
215STATIC int
216dequeue(ptr)
217struct use_item	*ptr;
218{
219	if (ptr->fptr == NULL)
220	    use_list.tail = ptr->bptr;
221	else
222	    (ptr->fptr)->bptr = ptr->bptr;
223
224	if (ptr->bptr == NULL)
225	    use_list.head = ptr->fptr;
226	else
227	    (ptr->bptr)->fptr = ptr->fptr;
228
229	use_count --;
230}
231
232
233
234/*f
235 *	Write out the compiled entry to the given file.
236 *	Return 0 if OK or -1 if not.
237 */
238STATIC int
239write_object(fp)
240FILE *fp;
241{
242	int i, tlength;
243    	__t_term_header header;
244	char *tnames, zero = '\0';
245
246	tnames = string_table + term_names;
247	tlength = strlen(tnames) + 1;
248	if (TERM_NAMES_LENGTH < tlength)
249		tlength = TERM_NAMES_LENGTH;
250	if (must_swap()) {
251		header.magic = swap(TERMINFO_MAGIC);
252		header.name_size = swap(tlength);
253		header.bool_count = swap(BOOLCOUNT);
254		header.num_count = swap(NUMCOUNT);
255		header.str_count = swap(STRCOUNT);
256		header.str_size = swap(next_free);
257	} else {
258		header.magic = TERMINFO_MAGIC;
259		header.name_size = tlength;
260		header.bool_count = BOOLCOUNT;
261		header.num_count = NUMCOUNT;
262		header.str_count = STRCOUNT;
263		header.str_size = next_free;
264	}
265
266	if (fwrite(&header, sizeof (header), 1, fp) != 1
267	|| fwrite(tnames, sizeof (char), tlength, fp) != tlength
268	|| fwrite(boolean, sizeof (char), BOOLCOUNT, fp) != BOOLCOUNT)
269		return (-1);
270
271	if ((tlength+BOOLCOUNT) % 2 != 0
272	&& fwrite(&zero, sizeof (char), 1, fp) != 1)
273		return (-1);
274
275	if (must_swap()) {
276		for (i = 0; i < NUMCOUNT; ++i)
277			number[i] = swap(number[i]);
278		for (i = 0; i < STRCOUNT; ++i)
279			string[i] = swap(string[i]);
280	}
281
282	if (fwrite(number, sizeof (short), NUMCOUNT, fp) != NUMCOUNT
283	|| fwrite(string, sizeof (short), STRCOUNT, fp) != STRCOUNT
284	|| fwrite(string_table, sizeof (char), next_free, fp) != next_free)
285		return (-1);
286	return (0);
287}
288
289
290
291/*f
292 *	Save the compiled version of a description in the filesystem.
293 *
294 *	make a copy of the name-list
295 *	break it up into first-name and all-but-last-name
296 *	creat(first-name)
297 *	write object information to first-name
298 *	close(first-name)
299 *      for each name in all-but-last-name
300 *	    link to first-name
301 *
302 */
303STATIC void
304dump_structure()
305{
306	FILE *fp;
307	struct stat sb;
308	char *p, *q, *first, *fn, *long_name, dir[2], tname[TERM_NAMES_LENGTH];
309
310	/* Bag copy of terminal name list.  Parse off the last name,
311	 * which should be the terminal's long name.  Parse off the
312	 * first name to be used for the terminal filename.
313	 */
314	(void) strncpy(tname, string_table + term_names, TERM_NAMES_LENGTH);
315	DEBUG(7, "Terminal names are \"%s\".\n", tname);
316	for (p = tname + strlen(tname); tname < p; --p) {
317		if (*p == '|') {
318			long_name = ++p;
319			break;
320		}
321	}
322	if (tname == p)
323		long_name = tname;
324	for (p = tname; p < long_name; ++p) {
325		if (*p == '|') {
326			if (tname < p)
327				*p++ = '\0';
328			break;
329		}
330	}
331	if (check_only) {
332		DEBUG(1, "Checked \"%s\".\n", tname);
333		return;
334	}
335	DEBUG(7, "Terminfo file name is \"%s\".\n", tname);
336	DEBUG(7, "Terminal's long name is \"%s\".\n", long_name);
337
338	/* Create terminfo object file. */
339	check_name(tname);
340	*dir = tolower(*tname);
341	dir[1] = '\0';
342	first = m_pathcat(dir, tname);
343	if (first == NULL)
344		err_abort(m_strmsg(long_path), *tname, tname);
345	if (0 <= stat(first, &sb) && start_time <= sb.st_mtime)
346		warning(m_strmsg(more_than_one), tname);
347	if (access(first, W_OK) == -1 && errno != ENOENT) {
348		perror(first);
349		err_abort(m_strmsg(write_err), first);
350	}
351	(void) unlink(first);
352	if ((fp = fopen(first, "w")) == NULL)
353		err_abort(m_strmsg(fail_open), first);
354	DEBUG(1, "Created \"%s\".\n", first);
355	if (write_object(fp) < 0)
356		err_abort(m_strmsg(write_err), first);
357	(void) fclose(fp);
358
359	/* Create links for alternate names. */
360	while (p < long_name) {
361		for (q = p; p < long_name; ++p) {
362			if (*p == '|') {
363				*p++ = '\0';
364				break;
365			}
366		}
367		check_name(q);
368		*dir = tolower(*q);
369		dir[1] = '\0';
370		fn = m_pathcat(dir, q);
371		if (fn == NULL) {
372			warning(m_strmsg(long_path), *q, q);
373			continue;
374		}
375		if (strcmp(q, tname) == 0) {
376			warning(m_strmsg(synonym), tname);
377			continue;
378		}
379		if (0 <= stat(fn, &sb) && start_time <= sb.st_mtime) {
380			warning(m_strmsg(more_than_one), q);
381			continue;
382		}
383		if (access(fn, W_OK) == -1 && errno != ENOENT) {
384			err_abort(m_strmsg(write_err), fn);
385		}
386		(void) unlink(fn);
387		if (link(first, fn) < 0) {
388			if ((fp = fopen(fn, "w")) == NULL)
389				err_abort(m_strmsg(fail_open), fn);
390			DEBUG(1, "Created \"%s\".\n", fn);
391			if (write_object(fp) < 0)
392				err_abort(m_strmsg(write_err), fn);
393			(void) fclose(fp);
394		} else {
395			DEBUG(1, "Linked \"%s\".\n", fn);
396		}
397		free(fn);
398	}
399	free(first);
400}
401
402
403/*f
404 *	copy string into next free part of string_table, doing a realloc()
405 *	if necessary.  return offset of beginning of string from start of
406 *	string_table.
407 */
408STATIC int
409save_str(string)
410char	*string;
411{
412	int	old_next_free = next_free;
413
414	if (table_size == 0)
415	{
416	    if ((string_table = malloc(1024)) == NULL)
417		syserr_abort(m_strmsg(nomem));
418	    table_size = 1024;
419	    DEBUG(5, "Made initial string table allocation.  Size is %d\n",
420								    table_size);
421	}
422
423	while (table_size < next_free + strlen(string))
424	{
425	    if ((string_table = realloc(string_table, table_size + 1024))
426									== NULL)
427		syserr_abort(m_strmsg(nomem));
428	    table_size += 1024;
429	    DEBUG(5, "Extended string table.  Size now %d\n", table_size);
430	}
431
432	strcpy(&string_table[next_free], string);
433	DEBUG(7, "Saved string '%s' ", string);
434	DEBUG(7, "at location %d\n", next_free);
435	next_free += strlen(string) + 1;
436
437	return (old_next_free);
438}
439
440/*f
441 *	Merge the compiled file whose name is in cur_token.valstring
442 *	with the current entry.
443 *
444 *		if it's a forward use-link
445 *	    	    if item_ptr == NULL
446 *		        queue it up for later handling
447 *	            else
448 *		        ignore it (we're already going through the queue)
449 *	        else it's a backward use-link
450 *	            read in the object file for that terminal
451 *	            merge contents with current structure
452 *
453 *	Returned value is 0 if it was a backward link and we
454 *	successfully read it in, -1 if a forward link.
455 */
456STATIC int
457handle_use(item_ptr, entry_offset)
458struct use_item	*item_ptr;
459long entry_offset;
460{
461        int i, err;
462	struct stat sb;
463	char *filename, dir[2];
464
465	check_name(curr_token.tk_valstring);
466	*dir = tolower(*curr_token.tk_valstring);
467	dir[1] = '\0';
468	filename = m_pathcat(dir, curr_token.tk_valstring);
469	if (filename == NULL) {
470		err_abort(
471			m_strmsg(long_path),
472			*curr_token.tk_valstring, curr_token.tk_valstring
473		);
474	}
475	if (stat(filename, &sb) < 0
476	|| (part2 == 0 && sb.st_mtime < start_time)) {
477		DEBUG(2, "Forward USE to %s", curr_token.tk_valstring);
478		if (item_ptr == NULL) {
479			DEBUG(2, " (enqueued)\n", "");
480			enqueue(entry_offset);
481		} else {
482			DEBUG(2, " (skipped)\n", "");
483		}
484		free(filename);
485		return (-1);
486	}
487	DEBUG(2, "Backward USE to %s\n", curr_token.tk_valstring);
488	(void) setupterm(curr_token.tk_valstring, STDOUT_FILENO, &err);
489	switch (err) {
490	case 1:
491		for (i = 0; i < BOOLCOUNT; ++i) {
492			if (boolean[i] == 0 && cur_term->Booleans[i])
493				boolean[i] = 1;
494		}
495		for (i = 0; i < NUMCOUNT; ++i) {
496			if (number[i] == -1 && cur_term->Numbers[i] != -1)
497				number[i] = cur_term->Numbers[i];
498		}
499		for (i = 0; i < STRCOUNT; ++i) {
500			if (string[i] == -1 && cur_term->Strings[i] != NULL)
501				string[i] = save_str(cur_term->Strings[i]);
502		}
503		(void) del_curterm(cur_term);
504		free(filename);
505		break;
506	case 0:
507		err_abort(m_strmsg(unknown_term), filename);
508		exit(BAD_TERMINAL);
509	case -1:
510		err_abort(m_strmsg(no_terminfo));
511		exit(BAD_TERMINAL);
512	}
513	return (0);
514}
515
516
517
518/*f
519 *	Compile one entry.  During the first pass, item_ptr is NULL.  In pass
520 *	two, item_ptr points to the current entry in the use_list.
521 *
522 *	found-forward-use = FALSE
523 *	re-initialise internal arrays
524 *	save names in string_table
525 *	get_token()
526 *	while (not EOF and not NAMES)
527 *	    if found-forward-use
528 *		do nothing
529 *	    else if 'use'
530 *		if handle_use() < 0
531 *		    found-forward-use = TRUE
532 *          else
533 *	        check for existance and type-correctness
534 *	        enter cap into structure
535 *	        if STRING
536 *	            save string in string_table
537 *	    get_token()
538 *      if ! found-forward-use
539 *	    clear CANCELS out of the structure
540 *	    dump compiled entry into filesystem
541 */
542STATIC int
543do_entry(item_ptr)
544struct use_item	*item_ptr;
545{
546	void *array;
547	long entry_offset;
548	int i, index;
549	register int token_type;
550	int found_forward_use = 0;
551
552	reset();
553	next_free = 0;
554
555	complete = 0;
556	term_names = save_str(curr_token.tk_name);
557	DEBUG(2, "Starting '%s'\n", curr_token.tk_name);
558	entry_offset = curr_file_pos;
559
560	for (token_type = get_token();
561	token_type != EOF && token_type != NAMES;
562	token_type = get_token()) {
563		if (found_forward_use) {
564			;
565		} else if (strcmp(curr_token.tk_name, "use") == 0) {
566			if (handle_use(item_ptr, entry_offset) < 0)
567				found_forward_use = 1;
568		} else {
569			if (find(curr_token.tk_name, &array, &index) < 0) {
570				warning(
571					m_strmsg(unknown_cap),
572					curr_token.tk_name
573				);
574				continue;
575			}
576			switch (token_type) {
577			case CANCEL:
578				if (array == boolean)
579					boolean[index] = 2;
580				else
581					((short*) array)[index] = -2;
582				continue;
583			case BOOLEAN:
584				if (array == boolean) {
585					boolean[index] = 1;
586					continue;
587				}
588				break;
589			case NUMBER:
590				if (array == number) {
591					number[index] = curr_token.tk_valnumber;
592					continue;
593				}
594				break;
595			case STRING:
596				if (array == string) {
597					string[index] = save_str(
598						curr_token.tk_valstring
599					);
600					continue;
601				}
602				break;
603			default:
604				warning(m_strmsg(unknown_token));
605				panic_mode(',');
606				continue;
607			}
608			warning(m_strmsg(wrong_type), curr_token.tk_name);
609		}
610	}
611	if (found_forward_use)
612		return (token_type);
613
614	/* Changed canceled values into in-active values. */
615	for (i = 0; i < BOOLCOUNT; ++i)
616		if (boolean[i] == 2)
617			boolean[i] = 0;
618	for (i = 0; i < NUMCOUNT; ++i)
619		if (number[i] == -2)
620			number[i] = -1;
621	for (i = 0; i < STRCOUNT; ++i)
622		if (string[i] == -2)
623			string[i] = -1;
624	dump_structure();
625	complete = 1;
626	return (token_type);
627}
628
629
630
631/*f
632 *	Main loop of the compiler.
633 *
634 *	get_token()
635 *	if curr_token != NAMES
636 *	    err_abort()
637 *	while (not at end of file)
638 *	    do an entry
639 */
640void
641compile()
642{
643	char			line[1024];
644	int			token_type;
645	struct use_item	*ptr;
646	int			old_use_count;
647
648	token_type = get_token();
649
650	if (token_type != NAMES)
651		err_abort(m_strmsg(bad_start));
652
653	while (token_type != EOF)
654	    token_type = do_entry(NULL);
655
656	DEBUG(2, "Starting handling of forward USE's\n", "");
657
658	for (part2=0; part2<2; part2++) {
659	    old_use_count = -1;
660
661	    DEBUG(2, "\n\nPART %d\n\n", part2);
662
663	    while (use_list.head != NULL && old_use_count != use_count)
664	    {
665		old_use_count = use_count;
666		for (ptr = use_list.tail; ptr != NULL; ptr = ptr->bptr)
667		{
668		    fseek(stdin, ptr->offset, 0);
669		    reset_input();
670		    if ((token_type = get_token()) != NAMES)
671			syserr_abort(m_strmsg(not_names));
672		    (void) do_entry(ptr);
673		    if (complete)
674			dequeue(ptr);
675		}
676
677		for (ptr = use_list.head; ptr != NULL; ptr = ptr->fptr)
678		{
679		    fseek(stdin, ptr->offset, 0);
680		    reset_input();
681		    if ((token_type = get_token()) != NAMES)
682			syserr_abort(m_strmsg(not_names));
683		    (void) do_entry(ptr);
684		    if (complete)
685			dequeue(ptr);
686		}
687
688		DEBUG(2,"Finished a pass through enqueued forward USE's\n","");
689	    }
690	}
691
692	if (use_list.head != NULL) {
693		fprintf(stderr, use_links);
694		for (ptr = use_list.head; ptr != NULL; ptr = ptr->fptr) {
695			fseek(stdin, ptr->offset, 0);
696			fgets(line, 1024, stdin);
697			fprintf(stderr, "%s", line);
698		}
699		exit(1);
700	}
701}
702