1/*
2 *       Copyright 1988 by Evans & Sutherland Computer Corporation,
3 *                          Salt Lake City, Utah
4 *  Portions Copyright 1989 by the Massachusetts Institute of Technology
5 *                        Cambridge, Massachusetts
6 *
7 * Copyright 1992 Claude Lecommandeur.
8 */
9
10/***********************************************************************
11 *
12 * $XConsortium: parse.c,v 1.52 91/07/12 09:59:37 dave Exp $
13 *
14 * parse the .twmrc file
15 *
16 * 17-Nov-87 Thomas E. LaStrange       File created
17 * 10-Oct-90 David M. Sternlicht       Storing saved colors on root
18 *
19 * Do the necessary modification to be integrated in ctwm.
20 * Can no longer be used for the standard twm.
21 *
22 * 22-April-92 Claude Lecommandeur.
23 *
24 ***********************************************************************/
25
26#include "ctwm.h"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <strings.h>
32#ifdef USEM4
33# include <sys/types.h>
34# include <sys/wait.h>
35#endif
36
37#include "ctwm_atoms.h"
38#include "screen.h"
39#include "parse.h"
40#include "parse_int.h"
41#include "deftwmrc.h"
42#ifdef SOUNDS
43#  include "sound.h"
44#endif
45
46#ifndef SYSTEM_INIT_FILE
47#error "No SYSTEM_INIT_FILE set"
48#endif
49
50static bool ParseStringList(const char **sl);
51
52/*
53 * With current bison, this is defined in the gram.tab.h, so this causes
54 * a warning for redundant declaration.  With older bisons and byacc,
55 * it's not, so taking it out causes a warning for implicit declaration.
56 * A little looking around doesn't show any handy #define's we could use
57 * to be sure of the difference.  This should quiet it down on gcc/clang
58 * anyway...
59 */
60#ifdef __GNUC__
61# pragma GCC diagnostic push
62# pragma GCC diagnostic ignored "-Wredundant-decls"
63extern int yyparse(void);
64# pragma GCC diagnostic pop
65#else
66extern int yyparse(void);
67#endif
68
69// Because of how this winds up shared with callback funcs in the
70// parsing, it's difficult to unwind from being global, so just accept
71// it.
72static FILE *twmrc;
73
74static int ptr = 0;
75static int len = 0;
76#define BUF_LEN 300
77static char buff[BUF_LEN + 1];
78static const char **stringListSource, *currentString;
79
80#ifdef NON_FLEX_LEX
81/*
82 * While these are (were) referenced in a number of places through the
83 * file, overflowlen is initialized to 0, only possibly changed in
84 * twmUnput(), and unless it's non-zero, neither is otherwise touched.
85 * So this is purely a twmUnput()-related var, and with flex, never used
86 * for anything.
87 */
88static char overflowbuff[20];           /* really only need one */
89static int overflowlen;
90#endif
91
92int ConstrainedMoveTime = 400;          /* milliseconds, event times */
93bool ParseError;                        /* error parsing the .twmrc file */
94int RaiseDelay = 0;                     /* msec, for AutoRaise */
95int (*twmInputFunc)(void);              /* used in lexer */
96
97static int twmrc_lineno;
98
99
100/* Actual file loader */
101static int ParseTwmrc(const char *filename);
102
103/* lex plumbing funcs */
104static bool doparse(int (*ifunc)(void), const char *srctypename,
105                    const char *srcname);
106
107static int twmStringListInput(void);
108#ifndef USEM4
109static int twmFileInput(void);
110#else
111static int m4twmFileInput(void);
112#endif
113
114#if defined(YYDEBUG) && YYDEBUG
115int yydebug = 1;
116#endif
117
118
119/**
120 * Principal entry point from top-level code to parse the config file.
121 * This tries the various permutations of config files we could load.
122 * For most possible names, we try loading `$NAME.$SCREENNUM` before
123 * trying `$NAME`.  If a `-f filename` is given on the command line, it's
124 * passed in here, and the normal `~/.[c]twmrc*` attempts are skipped if
125 * it's not found.
126 *
127 * \param filename A filename given in the -f command-line argument (or
128 * NULL)
129 * \return true/false for whether a valid config was parsed out from
130 * somewhere.
131 */
132bool
133LoadTwmrc(const char *filename)
134{
135	int ret = -1;
136	char *tryname = NULL;
137
138	/*
139	 * Check for the twmrc file in the following order:
140	 *   0.  -f filename.#
141	 *   1.  -f filename
142	 *       (skip to 6 if -f was given)
143	 *   2.  .ctwmrc.#
144	 *   3.  .ctwmrc
145	 *   4.  .twmrc.#
146	 *   5.  .twmrc
147	 *   6.  system.ctwmrc
148	 */
149#define TRY(fn) if((ret = ParseTwmrc(fn)) != -1) { goto DONE_TRYING; }  (void)0
150
151	if(filename) {
152		/* -f filename.# */
153		asprintf(&tryname, "%s.%d", filename, Scr->screen);
154		if(tryname == NULL) {
155			// Oh, we're _screwed_...
156			return false;
157		}
158		TRY(tryname);
159
160		/* -f filename */
161		TRY(filename);
162
163		/* If we didn't get either from -f, don't try the ~ bits */
164		goto TRY_FALLBACK;
165	}
166
167	if(Home) {
168		/* ~/.ctwmrc.screennum */
169		free(tryname);
170		asprintf(&tryname, "%s/.ctwmrc.%d", Home, Scr->screen);
171		if(tryname == NULL) {
172			return false;
173		}
174		TRY(tryname);
175
176		// All later attempts are guaranteed shorter strings than that,
177		// so we can just keep sprintf'ing over it.
178
179		/* ~/.ctwmrc */
180		sprintf(tryname, "%s/.ctwmrc", Home);
181		TRY(tryname);
182
183		/* ~/.twmrc.screennum */
184		sprintf(tryname, "%s/.twmrc.%d", Home, Scr->screen);
185		TRY(tryname);
186
187		/* ~/.twmrc */
188		sprintf(tryname, "%s/.twmrc", Home);
189		TRY(tryname);
190	}
191
192TRY_FALLBACK:
193	/* system.twmrc */
194	if((ret = ParseTwmrc(SYSTEM_INIT_FILE)) != -1) {
195		if(ret && filename) {
196			// If we were -f'ing, fell back to the system default, and
197			// that succeeeded, we warn.  It's "normal"(ish) to not have
198			// a personal twmrc and fall back...
199			fprintf(stderr,
200			        "%s:  unable to open twmrc file %s, using %s instead\n",
201			        ProgramName, filename, SYSTEM_INIT_FILE);
202		}
203		goto DONE_TRYING;
204	}
205
206
207DONE_TRYING:
208#undef TRY
209	free(tryname);
210
211	/*
212	 * If we wound up with -1 all the way, we totally failed to find a
213	 * file to work with.  Fall back to builtin config.
214	 */
215	if(ret == -1) {
216		// Only warn if -f.
217		if(filename) {
218			fprintf(stderr,
219			        "%s:  unable to open twmrc file %s, using built-in defaults instead\n",
220			        ProgramName, filename);
221		}
222		return ParseStringList(defTwmrc);
223	}
224
225
226	/* Better have a useful value in ret... */
227	return ret;
228}
229
230
231/**
232 * Try parsing a file as a ctwmrc.
233 *
234 * \param filename The filename to try opening and parsing.
235 * \return -1,0,1.  0/1 should be treated as false/true for whether
236 * parsing the file succeeded.  -1 means the file couldn't be opened.
237 */
238static int
239ParseTwmrc(const char *filename)
240{
241	bool status;
242
243#if 0
244	fprintf(stderr, "%s(): Trying %s\n", __func__, filename);
245#endif
246
247	/* See if we can open the file */
248	if(!filename) {
249		return -1;
250	}
251	twmrc = fopen(filename, "r");
252	if(!twmrc) {
253		return -1;
254	}
255
256
257	/* Got it.  Kick off the parsing, however we do it. */
258#ifdef USEM4
259	FILE *raw = NULL;
260	if(CLarg.GoThroughM4) {
261		/*
262		 * Hold onto raw filehandle so we can fclose() it below, and
263		 * swap twmrc over to the output from m4
264		 */
265		raw = twmrc;
266		twmrc = start_m4(raw);
267	}
268	status = doparse(m4twmFileInput, "file", filename);
269	fclose(twmrc);
270	if(raw) {
271		fclose(raw);
272	}
273#else
274	status = doparse(twmFileInput, "file", filename);
275	fclose(twmrc);
276#endif
277
278	/* And we're done */
279	return status;
280
281	/* NOTREACHED */
282}
283
284static bool
285ParseStringList(const char **sl)
286{
287	stringListSource = sl;
288	currentString = *sl;
289	return doparse(twmStringListInput, "string list", NULL);
290}
291
292
293/*
294 * Util used throughout the code (possibly often wrongly?)
295 */
296void twmrc_error_prefix(void)
297{
298	fprintf(stderr, "%s:  line %d:  ", ProgramName, twmrc_lineno);
299}
300
301
302
303/*
304 * Everything below here is related to plumbing and firing off lex/yacc
305 */
306
307
308/*
309 * Backend func that takes an input-providing func and hooks it up to the
310 * lex/yacc parser to do the work
311 */
312static bool
313doparse(int (*ifunc)(void), const char *srctypename,
314        const char *srcname)
315{
316	ptr = 0;
317	len = 0;
318	twmrc_lineno = 0;
319	ParseError = false;
320	twmInputFunc = ifunc;
321#ifdef NON_FLEX_LEX
322	overflowlen = 0;
323#endif
324
325	yyparse();
326
327	if(ParseError) {
328		fprintf(stderr, "%s:  errors found in twm %s",
329		        ProgramName, srctypename);
330		if(srcname) {
331			fprintf(stderr, " \"%s\"", srcname);
332		}
333		fprintf(stderr, "\n");
334	}
335	return !(ParseError);
336}
337
338
339/*
340 * Various input routines for the lexer for the various sources of
341 * config.
342 */
343
344#ifndef USEM4
345#include <ctype.h>
346
347/* This has Tom's include() funtionality.  This is utterly useless if you
348 * can use m4 for the same thing.               Chris P. Ross */
349
350#define MAX_INCLUDES 10
351
352static struct incl {
353	FILE *fp;
354	char *name;
355	int lineno;
356} rc_includes[MAX_INCLUDES];
357static int include_file = 0;
358
359
360static int twmFileInput(void)
361{
362#ifdef NON_FLEX_LEX
363	if(overflowlen) {
364		return (int) overflowbuff[--overflowlen];
365	}
366#endif
367
368	while(ptr == len) {
369		while(include_file) {
370			if(fgets(buff, BUF_LEN, rc_includes[include_file].fp) == NULL) {
371				free(rc_includes[include_file].name);
372				fclose(rc_includes[include_file].fp);
373				twmrc_lineno = rc_includes[include_file--].lineno;
374			}
375			else {
376				break;
377			}
378		}
379
380		if(!include_file)
381			if(fgets(buff, BUF_LEN, twmrc) == NULL) {
382				return 0;
383			}
384		twmrc_lineno++;
385
386		if(strncmp(buff, "include", 7) == 0) {
387			/* Whoops, an include file! */
388			char *p = buff + 7, *q;
389			FILE *fp;
390
391			while(isspace(*p)) {
392				p++;
393			}
394			for(q = p; *q && !isspace(*q); q++) {
395				continue;
396			}
397			*q = 0;
398
399			if((fp = fopen(p, "r")) == NULL) {
400				fprintf(stderr, "%s: Unable to open included init file %s\n",
401				        ProgramName, p);
402				continue;
403			}
404			if(++include_file >= MAX_INCLUDES) {
405				fprintf(stderr, "%s: init file includes nested too deep\n",
406				        ProgramName);
407				continue;
408			}
409			rc_includes[include_file].fp = fp;
410			rc_includes[include_file].lineno = twmrc_lineno;
411			twmrc_lineno = 0;
412			rc_includes[include_file].name = strdup(p);
413			continue;
414		}
415		ptr = 0;
416		len = strlen(buff);
417	}
418	return ((int)buff[ptr++]);
419}
420#else /* USEM4 */
421/* If you're going to use m4, use this version instead.  Much simpler.
422 * m4 ism's credit to Josh Osborne (stripes) */
423
424static int m4twmFileInput(void)
425{
426	int line;
427	static FILE *cp = NULL;
428
429	if(cp == NULL && CLarg.keepM4_filename) {
430		cp = fopen(CLarg.keepM4_filename, "w");
431		if(cp == NULL) {
432			fprintf(stderr,
433			        "%s:  unable to create m4 output %s, ignoring\n",
434			        ProgramName, CLarg.keepM4_filename);
435			CLarg.keepM4_filename = NULL;
436		}
437	}
438
439#ifdef NON_FLEX_LEX
440	if(overflowlen) {
441		return((int) overflowbuff[--overflowlen]);
442	}
443#endif
444
445	while(ptr == len) {
446nextline:
447		if(fgets(buff, BUF_LEN, twmrc) == NULL) {
448			if(cp) {
449				fclose(cp);
450			}
451			return(0);
452		}
453		if(cp) {
454			fputs(buff, cp);
455		}
456
457		if(sscanf(buff, "#line %d", &line)) {
458			twmrc_lineno = line - 1;
459			goto nextline;
460		}
461		else {
462			twmrc_lineno++;
463		}
464
465		ptr = 0;
466		len = strlen(buff);
467	}
468	return ((int)buff[ptr++]);
469}
470#endif /* USEM4 */
471
472
473static int twmStringListInput(void)
474{
475#ifdef NON_FLEX_LEX
476	if(overflowlen) {
477		return (int) overflowbuff[--overflowlen];
478	}
479#endif
480
481	/*
482	 * return the character currently pointed to
483	 */
484	if(currentString) {
485		unsigned int c = (unsigned int) * currentString++;
486
487		if(c) {
488			return c;        /* if non-nul char */
489		}
490		currentString = *++stringListSource;  /* advance to next bol */
491		return '\n';                    /* but say that we hit last eol */
492	}
493	return 0;                           /* eof */
494}
495
496
497
498/*
499 * unput/output funcs for AT&T lex.  No longer supported, and expected to
500 * be GC'd in a release or two.
501 */
502#ifdef NON_FLEX_LEX
503
504void twmUnput(int c)
505{
506	if(overflowlen < sizeof overflowbuff) {
507		overflowbuff[overflowlen++] = (char) c;
508	}
509	else {
510		twmrc_error_prefix();
511		fprintf(stderr, "unable to unput character (%c)\n",
512		        c);
513	}
514}
515
516void TwmOutput(int c)
517{
518	putchar(c);
519}
520
521#endif /* NON_FLEX_LEX */
522