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