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