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