Whamcloud - gitweb
Merge tag 'v1.46.1' into debian/master
[tools/e2fsprogs.git] / util / subst.c
1 /*
2  * subst.c --- substitution program
3  *
4  * Subst is used as a quickie program to do @ substitutions
5  *
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #else
11 #define HAVE_SYS_STAT_H
12 #define HAVE_SYS_TIME_H
13 #endif
14 #include <stdio.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <string.h>
19 #include <ctype.h>
20 #ifdef HAVE_SYS_TIME_H
21 #include <sys/time.h>
22 #endif
23 #ifdef HAVE_SYS_TYPES_H
24 #include <sys/types.h>
25 #endif
26 #ifdef HAVE_SYS_STAT_H
27 #include <sys/stat.h>
28 #endif
29 #include <fcntl.h>
30 #include <time.h>
31 #include <utime.h>
32 #ifdef HAVE_SYS_TIME_H
33 #include <sys/time.h>
34 #endif
35
36 #ifdef HAVE_GETOPT_H
37 #include <getopt.h>
38 #else
39 extern char *optarg;
40 extern int optind;
41 #endif
42
43
44 struct subst_entry {
45         char *name;
46         char *value;
47         struct subst_entry *next;
48 };
49
50 static struct subst_entry *subst_table = 0;
51
52 static int add_subst(char *name, char *value)
53 {
54         struct subst_entry      *ent = 0;
55
56         ent = (struct subst_entry *) malloc(sizeof(struct subst_entry));
57         if (!ent)
58                 goto fail;
59         ent->name = (char *) malloc(strlen(name)+1);
60         if (!ent->name)
61                 goto fail;
62         ent->value = (char *) malloc(strlen(value)+1);
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) {
72                 free(ent->name);
73                 free(ent);
74         }
75         return ENOMEM;
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
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  */
93 static char *get_subst_symbol(const char *begin, size_t len, char prefix)
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;
106
107         /*
108          * The substitution variable must all be in the of [0-9A-Za-z_].
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') &&
114                     !(*cp >= '0' && *cp <= '9') &&
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;
127         if (replace_len == 0)
128                 memmove(begin, end+1, strlen(end)+1);
129         else if (replace_len != len+1)
130                 memmove(end+(replace_len-len-1), end,
131                         strlen(end)+1);
132         memcpy(begin, newstr, replace_len);
133 }
134
135 static void substitute_line(char *line)
136 {
137         char    *ptr, *name_ptr, *end_ptr;
138         struct subst_entry *ent;
139         char    *replace_name;
140         size_t  len;
141
142         /*
143          * Expand all @FOO@ substitutions
144          */
145         ptr = line;
146         while (ptr) {
147                 name_ptr = strchr(ptr, '@');
148                 if (!name_ptr)
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, '@');
159                 if (!end_ptr)
160                         break;
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                 }
167                 ent = fetch_subst_entry(replace_name);
168                 if (!ent) {
169                         fprintf(stderr, "Unfound expansion: '%s'\n",
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
178                 ptr = name_ptr-1;
179                 replace_string(ptr, end_ptr, ent->value);
180                 if ((ent->value[0] == '@') &&
181                     (strlen(replace_name) == strlen(ent->value)-2) &&
182                     !strncmp(replace_name, ent->value+1,
183                              strlen(ent->value)-2))
184                         /* avoid an infinite loop */
185                         ptr += strlen(ent->value);
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;
208                 }
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);
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                  */
262                 if (*ptr == '@')
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
275                 printf("Substitute: '%s' for '%s'\n", ptr, cp ? cp : "<NULL>");
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  */
284 static int compare_file(FILE *old_f, FILE *new_f)
285 {
286         char    oldbuf[2048], newbuf[2048], *oldcp, *newcp;
287         int     retval;
288
289         while (1) {
290                 oldcp = fgets(oldbuf, sizeof(oldbuf), old_f);
291                 newcp = fgets(newbuf, sizeof(newbuf), new_f);
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
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 }
321
322
323 int main(int argc, char **argv)
324 {
325         char    line[2048];
326         int     c;
327         int     fd, ofd = -1;
328         FILE    *in, *out, *old = NULL;
329         char    *outfn = NULL, *newfn = NULL;
330         int     verbose = 0;
331         int     adjust_timestamp = 0;
332         int     got_atime = 0;
333         struct stat stbuf;
334         struct timeval tv[2];
335
336         while ((c = getopt (argc, argv, "f:tv")) != EOF) {
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;
347                 case 't':
348                         adjust_timestamp++;
349                         break;
350                 case 'v':
351                         verbose++;
352                         break;
353                 default:
354                         fprintf(stderr, "%s: [-f config-file] [file]\n",
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;
368
369         if (optind < argc) {
370                 outfn = argv[optind];
371                 newfn = (char *) malloc(strlen(outfn)+20);
372                 if (!newfn) {
373                         fprintf(stderr, "Memory error!  Exiting.\n");
374                         exit(1);
375                 }
376                 strcpy(newfn, outfn);
377                 strcat(newfn, ".new");
378                 ofd = open(newfn, O_CREAT|O_TRUNC|O_RDWR, 0644);
379                 if (ofd < 0) {
380                         perror(newfn);
381                         exit(1);
382                 }
383                 out = fdopen(ofd, "w+");
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                 }
406         } else {
407                 out = stdout;
408                 outfn = 0;
409         }
410
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);
418         if (outfn) {
419                 fflush(out);
420                 rewind(out);
421                 if (old && compare_file(old, out)) {
422                         if (verbose)
423                                 printf("No change, keeping %s.\n", outfn);
424                         if (adjust_timestamp) {
425                                 if (verbose)
426                                         printf("Updating modtime for %s\n", outfn);
427                                 if (gettimeofday(&tv[1], NULL) < 0) {
428                                         perror("gettimeofday");
429                                         exit(1);
430                                 }
431                                 if (got_atime == 0)
432                                         tv[0] = tv[1];
433                                 else if (verbose)
434                                         printf("Using original atime\n");
435                                 set_utimes(outfn, fileno(old), tv);
436                         }
437                         if (ofd >= 0)
438                                 (void) fchmod(ofd, 0444);
439                         fclose(out);
440                         if (unlink(newfn) < 0)
441                                 perror("unlink");
442                 } else {
443                         if (verbose)
444                                 printf("Creating or replacing %s.\n", outfn);
445                         if (ofd >= 0)
446                                 (void) fchmod(ofd, 0444);
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                         }
455                 }
456         }
457         if (old)
458                 fclose(old);
459         if (newfn)
460                 free(newfn);
461         return (0);
462 }
463
464