1/*
2 * subst.c --- substitution program
3 *
4 * Subst is used as a quicky program to do @ substitutions
5 *
6 */
7
8#include <stdio.h>
9#include <errno.h>
10#include <stdlib.h>
11#include <unistd.h>
12#include <string.h>
13#include <ctype.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <time.h>
17#include <utime.h>
18
19#ifdef HAVE_GETOPT_H
20#include <getopt.h>
21#else
22extern char *optarg;
23extern int optind;
24#endif
25
26
27struct subst_entry {
28	char *name;
29	char *value;
30	struct subst_entry *next;
31};
32
33struct subst_entry *subst_table = 0;
34
35static int add_subst(char *name, char *value)
36{
37	struct subst_entry	*ent = 0;
38	int	retval;
39
40	retval = ENOMEM;
41	ent = (struct subst_entry *) malloc(sizeof(struct subst_entry));
42	if (!ent)
43		goto fail;
44	ent->name = (char *) malloc(strlen(name)+1);
45	if (!ent->name)
46		goto fail;
47	ent->value = (char *) malloc(strlen(value)+1);
48	if (!ent->value)
49		goto fail;
50	strcpy(ent->name, name);
51	strcpy(ent->value, value);
52	ent->next = subst_table;
53	subst_table = ent;
54	return 0;
55fail:
56	if (ent) {
57		if (ent->name)
58			free(ent->name);
59		if (ent->value)
60			free(ent->value);
61		free(ent);
62	}
63	return retval;
64}
65
66static struct subst_entry *fetch_subst_entry(char *name)
67{
68	struct subst_entry *ent;
69
70	for (ent = subst_table; ent; ent = ent->next) {
71		if (strcmp(name, ent->name) == 0)
72			break;
73	}
74	return ent;
75}
76
77/*
78 * Given the starting and ending position of the replacement name,
79 * check to see if it is valid, and pull it out if it is.
80 */
81static char *get_subst_symbol(const char *begin, size_t len, char prefix)
82{
83	static char replace_name[128];
84	char *cp, *start;
85
86	start = replace_name;
87	if (prefix)
88		*start++ = prefix;
89
90	if (len > sizeof(replace_name)-2)
91		return NULL;
92	memcpy(start, begin, len);
93	start[len] = 0;
94
95	/*
96	 * The substitution variable must all be in the of [0-9A-Za-z_].
97	 * If it isn't, this must be an invalid symbol name.
98	 */
99	for (cp = start; *cp; cp++) {
100		if (!(*cp >= 'a' && *cp <= 'z') &&
101		    !(*cp >= 'A' && *cp <= 'Z') &&
102		    !(*cp >= '0' && *cp <= '9') &&
103		    !(*cp == '_'))
104			return NULL;
105	}
106	return (replace_name);
107}
108
109static void replace_string(char *begin, char *end, char *newstr)
110{
111	int	replace_len, len;
112
113	replace_len = strlen(newstr);
114	len = end - begin;
115	if (replace_len == 0)
116		memmove(begin, end+1, strlen(end)+1);
117	else if (replace_len != len+1)
118		memmove(end+(replace_len-len-1), end,
119			strlen(end)+1);
120	memcpy(begin, newstr, replace_len);
121}
122
123static void substitute_line(char *line)
124{
125	char	*ptr, *name_ptr, *end_ptr;
126	struct subst_entry *ent;
127	char	*replace_name;
128	size_t	len;
129
130	/*
131	 * Expand all @FOO@ substitutions
132	 */
133	ptr = line;
134	while (ptr) {
135		name_ptr = strchr(ptr, '@');
136		if (!name_ptr)
137			break;	/* No more */
138		if (*(++name_ptr) == '@') {
139			/*
140			 * Handle tytso@@mit.edu --> tytso@mit.edu
141			 */
142			memmove(name_ptr-1, name_ptr, strlen(name_ptr)+1);
143			ptr = name_ptr+1;
144			continue;
145		}
146		end_ptr = strchr(name_ptr, '@');
147		if (!end_ptr)
148			break;
149		len = end_ptr - name_ptr;
150		replace_name = get_subst_symbol(name_ptr, len, 0);
151		if (!replace_name) {
152			ptr = name_ptr;
153			continue;
154		}
155		ent = fetch_subst_entry(replace_name);
156		if (!ent) {
157			fprintf(stderr, "Unfound expansion: '%s'\n",
158				replace_name);
159			ptr = end_ptr + 1;
160			continue;
161		}
162#if 0
163		fprintf(stderr, "Replace name = '%s' with '%s'\n",
164		       replace_name, ent->value);
165#endif
166		ptr = name_ptr-1;
167		replace_string(ptr, end_ptr, ent->value);
168		if ((ent->value[0] == '@') &&
169		    (strlen(replace_name) == strlen(ent->value)-2) &&
170		    !strncmp(replace_name, ent->value+1,
171			     strlen(ent->value)-2))
172			/* avoid an infinite loop */
173			ptr += strlen(ent->value);
174	}
175	/*
176	 * Now do a second pass to expand ${FOO}
177	 */
178	ptr = line;
179	while (ptr) {
180		name_ptr = strchr(ptr, '$');
181		if (!name_ptr)
182			break;	/* No more */
183		if (*(++name_ptr) != '{') {
184			ptr = name_ptr;
185			continue;
186		}
187		name_ptr++;
188		end_ptr = strchr(name_ptr, '}');
189		if (!end_ptr)
190			break;
191		len = end_ptr - name_ptr;
192		replace_name = get_subst_symbol(name_ptr, len, '$');
193		if (!replace_name) {
194			ptr = name_ptr;
195			continue;
196		}
197		ent = fetch_subst_entry(replace_name);
198		if (!ent) {
199			ptr = end_ptr + 1;
200			continue;
201		}
202#if 0
203		fprintf(stderr, "Replace name = '%s' with '%s'\n",
204		       replace_name, ent->value);
205#endif
206		ptr = name_ptr-2;
207		replace_string(ptr, end_ptr, ent->value);
208	}
209}
210
211static void parse_config_file(FILE *f)
212{
213	char	line[2048];
214	char	*cp, *ptr;
215
216	while (!feof(f)) {
217		memset(line, 0, sizeof(line));
218		if (fgets(line, sizeof(line), f) == NULL)
219			break;
220		/*
221		 * Strip newlines and comments.
222		 */
223		cp = strchr(line, '\n');
224		if (cp)
225			*cp = 0;
226		cp = strchr(line, '#');
227		if (cp)
228			*cp = 0;
229		/*
230		 * Skip trailing and leading whitespace
231		 */
232		for (cp = line + strlen(line) - 1; cp >= line; cp--) {
233			if (*cp == ' ' || *cp == '\t')
234				*cp = 0;
235			else
236				break;
237		}
238		cp = line;
239		while (*cp && isspace(*cp))
240			cp++;
241		ptr = cp;
242		/*
243		 * Skip empty lines
244		 */
245		if (*ptr == 0)
246			continue;
247		/*
248		 * Ignore future extensions
249		 */
250		if (*ptr == '@')
251			continue;
252		/*
253		 * Parse substitutions
254		 */
255		for (cp = ptr; *cp; cp++)
256			if (isspace(*cp))
257				break;
258		*cp = 0;
259		for (cp++; *cp; cp++)
260			if (!isspace(*cp))
261				break;
262#if 0
263		printf("Substitute: '%s' for '%s'\n", ptr, cp ? cp : "<NULL>");
264#endif
265		add_subst(ptr, cp);
266	}
267}
268
269/*
270 * Return 0 if the files are different, 1 if the files are the same.
271 */
272static int compare_file(const char *outfn, const char *newfn)
273{
274	FILE	*old_f, *new_f;
275	char	oldbuf[2048], newbuf[2048], *oldcp, *newcp;
276	int	retval;
277
278	old_f = fopen(outfn, "r");
279	if (!old_f)
280		return 0;
281	new_f = fopen(newfn, "r");
282	if (!new_f) {
283		fclose(old_f);
284		return 0;
285	}
286
287	while (1) {
288		oldcp = fgets(oldbuf, sizeof(oldbuf), old_f);
289		newcp = fgets(newbuf, sizeof(newbuf), new_f);
290		if (!oldcp && !newcp) {
291			retval = 1;
292			break;
293		}
294		if (!oldcp || !newcp || strcmp(oldbuf, newbuf)) {
295			retval = 0;
296			break;
297		}
298	}
299	fclose(old_f);
300	fclose(new_f);
301	return retval;
302}
303
304
305
306int main(int argc, char **argv)
307{
308	char	line[2048];
309	int	c;
310	FILE	*in, *out;
311	char	*outfn = NULL, *newfn = NULL;
312	int	verbose = 0;
313	int	adjust_timestamp = 0;
314	struct stat stbuf;
315	struct utimbuf ut;
316
317	while ((c = getopt (argc, argv, "f:tv")) != EOF) {
318		switch (c) {
319		case 'f':
320			in = fopen(optarg, "r");
321			if (!in) {
322				perror(optarg);
323				exit(1);
324			}
325			parse_config_file(in);
326			fclose(in);
327			break;
328		case 't':
329			adjust_timestamp++;
330			break;
331		case 'v':
332			verbose++;
333			break;
334		default:
335			fprintf(stderr, "%s: [-f config-file] [file]\n",
336				argv[0]);
337			break;
338		}
339	}
340	if (optind < argc) {
341		in = fopen(argv[optind], "r");
342		if (!in) {
343			perror(argv[optind]);
344			exit(1);
345		}
346		optind++;
347	} else
348		in = stdin;
349
350	if (optind < argc) {
351		outfn = argv[optind];
352		newfn = (char *) malloc(strlen(outfn)+20);
353		if (!newfn) {
354			fprintf(stderr, "Memory error!  Exiting.\n");
355			exit(1);
356		}
357		strcpy(newfn, outfn);
358		strcat(newfn, ".new");
359		out = fopen(newfn, "w");
360		if (!out) {
361			perror(newfn);
362			exit(1);
363		}
364	} else {
365		out = stdout;
366		outfn = 0;
367	}
368
369	while (!feof(in)) {
370		if (fgets(line, sizeof(line), in) == NULL)
371			break;
372		substitute_line(line);
373		fputs(line, out);
374	}
375	fclose(in);
376	fclose(out);
377	if (outfn) {
378		struct stat st;
379		if (compare_file(outfn, newfn)) {
380			if (verbose)
381				printf("No change, keeping %s.\n", outfn);
382			if (adjust_timestamp) {
383				if (stat(outfn, &stbuf) == 0) {
384					if (verbose)
385						printf("Updating modtime for %s\n", outfn);
386					ut.actime = stbuf.st_atime;
387					ut.modtime = time(0);
388					if (utime(outfn, &ut) < 0)
389						perror("utime");
390				}
391			}
392			unlink(newfn);
393		} else {
394			if (verbose)
395				printf("Creating or replacing %s.\n", outfn);
396			rename(newfn, outfn);
397		}
398		/* set read-only to alert user it is a generated file */
399		if (stat(outfn, &st) == 0)
400			chmod(outfn, st.st_mode & ~0222);
401	}
402	return (0);
403}
404
405
406