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