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