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