Whamcloud - gitweb
build: fix unused/uninitialized variable warnings
[tools/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 #ifdef HAVE_SYS_TIME_H
18 #include <sys/time.h>
19 #endif
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <time.h>
24 #include <utime.h>
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         return (0);
435 }
436
437