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