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