blob: 20dd6f21a1b63052eca22be12c29237d776ac519 [file] [log] [blame]
Theodore Ts'o44339bd1997-10-15 02:47:20 +00001/*
2 * subst.c --- substitution program
3 *
4 * Subst is used as a quicky program to do @ substitutions
Theodore Ts'oefc6f622008-08-27 23:07:54 -04005 *
Theodore Ts'o44339bd1997-10-15 02:47:20 +00006 */
7
8#include <stdio.h>
9#include <errno.h>
10#include <stdlib.h>
11#include <unistd.h>
12#include <string.h>
13#include <ctype.h>
Theodore Ts'odd607052004-04-03 13:53:46 -050014#include <sys/types.h>
15#include <sys/stat.h>
Matthias Andreef932ed92004-04-12 20:16:04 +020016#include <time.h>
Theodore Ts'odd607052004-04-03 13:53:46 -050017#include <utime.h>
Theodore Ts'o44339bd1997-10-15 02:47:20 +000018
19#ifdef HAVE_GETOPT_H
20#include <getopt.h>
Theodore Ts'o691d3352000-04-03 16:16:46 +000021#else
22extern char *optarg;
23extern int optind;
Theodore Ts'o44339bd1997-10-15 02:47:20 +000024#endif
25
26
27struct subst_entry {
28 char *name;
29 char *value;
30 struct subst_entry *next;
31};
32
JP Abgralle0ed7402014-03-19 19:08:39 -070033static struct subst_entry *subst_table = 0;
Theodore Ts'o44339bd1997-10-15 02:47:20 +000034
35static int add_subst(char *name, char *value)
36{
37 struct subst_entry *ent = 0;
Theodore Ts'oefc6f622008-08-27 23:07:54 -040038
Theodore Ts'o45d21611998-01-19 14:40:24 +000039 ent = (struct subst_entry *) malloc(sizeof(struct subst_entry));
Theodore Ts'o44339bd1997-10-15 02:47:20 +000040 if (!ent)
41 goto fail;
Theodore Ts'o45d21611998-01-19 14:40:24 +000042 ent->name = (char *) malloc(strlen(name)+1);
Theodore Ts'o44339bd1997-10-15 02:47:20 +000043 if (!ent->name)
44 goto fail;
Theodore Ts'o45d21611998-01-19 14:40:24 +000045 ent->value = (char *) malloc(strlen(value)+1);
Theodore Ts'o44339bd1997-10-15 02:47:20 +000046 if (!ent->value)
47 goto fail;
48 strcpy(ent->name, name);
49 strcpy(ent->value, value);
50 ent->next = subst_table;
51 subst_table = ent;
52 return 0;
53fail:
54 if (ent) {
Jim Meyering45e338f2009-02-23 18:07:50 +010055 free(ent->name);
Theodore Ts'o44339bd1997-10-15 02:47:20 +000056 free(ent);
57 }
JP Abgralle0ed7402014-03-19 19:08:39 -070058 return ENOMEM;
Theodore Ts'o44339bd1997-10-15 02:47:20 +000059}
60
61static struct subst_entry *fetch_subst_entry(char *name)
62{
63 struct subst_entry *ent;
64
65 for (ent = subst_table; ent; ent = ent->next) {
66 if (strcmp(name, ent->name) == 0)
67 break;
68 }
69 return ent;
70}
71
Theodore Ts'oe7549ca1998-08-01 04:35:39 +000072/*
73 * Given the starting and ending position of the replacement name,
74 * check to see if it is valid, and pull it out if it is.
75 */
Theodore Ts'o54434922003-12-07 01:28:50 -050076static char *get_subst_symbol(const char *begin, size_t len, char prefix)
Theodore Ts'oe7549ca1998-08-01 04:35:39 +000077{
78 static char replace_name[128];
79 char *cp, *start;
80
81 start = replace_name;
82 if (prefix)
83 *start++ = prefix;
84
85 if (len > sizeof(replace_name)-2)
86 return NULL;
87 memcpy(start, begin, len);
88 start[len] = 0;
Theodore Ts'oefc6f622008-08-27 23:07:54 -040089
Theodore Ts'oe7549ca1998-08-01 04:35:39 +000090 /*
Theodore Ts'o7822c1d1998-12-19 08:08:43 +000091 * The substitution variable must all be in the of [0-9A-Za-z_].
Theodore Ts'oe7549ca1998-08-01 04:35:39 +000092 * If it isn't, this must be an invalid symbol name.
93 */
94 for (cp = start; *cp; cp++) {
95 if (!(*cp >= 'a' && *cp <= 'z') &&
96 !(*cp >= 'A' && *cp <= 'Z') &&
Theodore Ts'o7822c1d1998-12-19 08:08:43 +000097 !(*cp >= '0' && *cp <= '9') &&
Theodore Ts'oe7549ca1998-08-01 04:35:39 +000098 !(*cp == '_'))
99 return NULL;
100 }
101 return (replace_name);
102}
103
104static void replace_string(char *begin, char *end, char *newstr)
105{
106 int replace_len, len;
107
108 replace_len = strlen(newstr);
109 len = end - begin;
Theodore Ts'o913c4e92001-04-17 05:05:37 +0000110 if (replace_len == 0)
111 memmove(begin, end+1, strlen(end)+1);
112 else if (replace_len != len+1)
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000113 memmove(end+(replace_len-len-1), end,
114 strlen(end)+1);
115 memcpy(begin, newstr, replace_len);
116}
117
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000118static void substitute_line(char *line)
119{
Theodore Ts'ocdceb041999-10-26 16:28:59 +0000120 char *ptr, *name_ptr, *end_ptr;
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000121 struct subst_entry *ent;
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000122 char *replace_name;
Theodore Ts'o54434922003-12-07 01:28:50 -0500123 size_t len;
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000124
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000125 /*
126 * Expand all @FOO@ substitutions
127 */
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000128 ptr = line;
129 while (ptr) {
130 name_ptr = strchr(ptr, '@');
131 if (!name_ptr)
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000132 break; /* No more */
133 if (*(++name_ptr) == '@') {
134 /*
135 * Handle tytso@@mit.edu --> tytso@mit.edu
136 */
137 memmove(name_ptr-1, name_ptr, strlen(name_ptr)+1);
138 ptr = name_ptr+1;
139 continue;
140 }
141 end_ptr = strchr(name_ptr, '@');
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000142 if (!end_ptr)
143 break;
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000144 len = end_ptr - name_ptr;
145 replace_name = get_subst_symbol(name_ptr, len, 0);
146 if (!replace_name) {
147 ptr = name_ptr;
148 continue;
149 }
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000150 ent = fetch_subst_entry(replace_name);
151 if (!ent) {
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400152 fprintf(stderr, "Unfound expansion: '%s'\n",
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000153 replace_name);
154 ptr = end_ptr + 1;
155 continue;
156 }
157#if 0
158 fprintf(stderr, "Replace name = '%s' with '%s'\n",
159 replace_name, ent->value);
160#endif
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000161 ptr = name_ptr-1;
162 replace_string(ptr, end_ptr, ent->value);
Theodore Ts'o98224fb2006-11-12 17:43:50 -0500163 if ((ent->value[0] == '@') &&
164 (strlen(replace_name) == strlen(ent->value)-2) &&
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400165 !strncmp(replace_name, ent->value+1,
Theodore Ts'o98224fb2006-11-12 17:43:50 -0500166 strlen(ent->value)-2))
167 /* avoid an infinite loop */
168 ptr += strlen(ent->value);
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000169 }
170 /*
171 * Now do a second pass to expand ${FOO}
172 */
173 ptr = line;
174 while (ptr) {
175 name_ptr = strchr(ptr, '$');
176 if (!name_ptr)
177 break; /* No more */
178 if (*(++name_ptr) != '{') {
179 ptr = name_ptr;
180 continue;
181 }
182 name_ptr++;
183 end_ptr = strchr(name_ptr, '}');
184 if (!end_ptr)
185 break;
186 len = end_ptr - name_ptr;
187 replace_name = get_subst_symbol(name_ptr, len, '$');
188 if (!replace_name) {
189 ptr = name_ptr;
190 continue;
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400191 }
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000192 ent = fetch_subst_entry(replace_name);
193 if (!ent) {
194 ptr = end_ptr + 1;
195 continue;
196 }
197#if 0
198 fprintf(stderr, "Replace name = '%s' with '%s'\n",
199 replace_name, ent->value);
200#endif
201 ptr = name_ptr-2;
202 replace_string(ptr, end_ptr, ent->value);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000203 }
204}
205
206static void parse_config_file(FILE *f)
207{
208 char line[2048];
209 char *cp, *ptr;
210
211 while (!feof(f)) {
212 memset(line, 0, sizeof(line));
213 if (fgets(line, sizeof(line), f) == NULL)
214 break;
215 /*
216 * Strip newlines and comments.
217 */
218 cp = strchr(line, '\n');
219 if (cp)
220 *cp = 0;
221 cp = strchr(line, '#');
222 if (cp)
223 *cp = 0;
224 /*
225 * Skip trailing and leading whitespace
226 */
227 for (cp = line + strlen(line) - 1; cp >= line; cp--) {
228 if (*cp == ' ' || *cp == '\t')
229 *cp = 0;
230 else
231 break;
232 }
233 cp = line;
234 while (*cp && isspace(*cp))
235 cp++;
236 ptr = cp;
237 /*
238 * Skip empty lines
239 */
240 if (*ptr == 0)
241 continue;
242 /*
243 * Ignore future extensions
244 */
Theodore Ts'oe7549ca1998-08-01 04:35:39 +0000245 if (*ptr == '@')
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000246 continue;
247 /*
248 * Parse substitutions
249 */
250 for (cp = ptr; *cp; cp++)
251 if (isspace(*cp))
252 break;
253 *cp = 0;
254 for (cp++; *cp; cp++)
255 if (!isspace(*cp))
256 break;
257#if 0
Theodore Ts'o913c4e92001-04-17 05:05:37 +0000258 printf("Substitute: '%s' for '%s'\n", ptr, cp ? cp : "<NULL>");
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000259#endif
260 add_subst(ptr, cp);
261 }
262}
263
264/*
265 * Return 0 if the files are different, 1 if the files are the same.
266 */
267static int compare_file(const char *outfn, const char *newfn)
268{
Theodore Ts'o45d21611998-01-19 14:40:24 +0000269 FILE *old_f, *new_f;
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000270 char oldbuf[2048], newbuf[2048], *oldcp, *newcp;
271 int retval;
272
Theodore Ts'o45d21611998-01-19 14:40:24 +0000273 old_f = fopen(outfn, "r");
274 if (!old_f)
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000275 return 0;
Theodore Ts'o45d21611998-01-19 14:40:24 +0000276 new_f = fopen(newfn, "r");
Brian Behlendorff1d6a072007-03-21 17:19:55 -0400277 if (!new_f) {
278 fclose(old_f);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000279 return 0;
Brian Behlendorff1d6a072007-03-21 17:19:55 -0400280 }
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000281
282 while (1) {
Theodore Ts'o45d21611998-01-19 14:40:24 +0000283 oldcp = fgets(oldbuf, sizeof(oldbuf), old_f);
284 newcp = fgets(newbuf, sizeof(newbuf), new_f);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000285 if (!oldcp && !newcp) {
286 retval = 1;
287 break;
288 }
289 if (!oldcp || !newcp || strcmp(oldbuf, newbuf)) {
290 retval = 0;
291 break;
292 }
293 }
Theodore Ts'o45d21611998-01-19 14:40:24 +0000294 fclose(old_f);
295 fclose(new_f);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000296 return retval;
297}
298
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000299
300
301int main(int argc, char **argv)
302{
303 char line[2048];
304 int c;
305 FILE *in, *out;
306 char *outfn = NULL, *newfn = NULL;
307 int verbose = 0;
Theodore Ts'odd607052004-04-03 13:53:46 -0500308 int adjust_timestamp = 0;
309 struct stat stbuf;
310 struct utimbuf ut;
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400311
Theodore Ts'odd607052004-04-03 13:53:46 -0500312 while ((c = getopt (argc, argv, "f:tv")) != EOF) {
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000313 switch (c) {
314 case 'f':
315 in = fopen(optarg, "r");
316 if (!in) {
317 perror(optarg);
318 exit(1);
319 }
320 parse_config_file(in);
321 fclose(in);
322 break;
Theodore Ts'odd607052004-04-03 13:53:46 -0500323 case 't':
324 adjust_timestamp++;
325 break;
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000326 case 'v':
327 verbose++;
328 break;
329 default:
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400330 fprintf(stderr, "%s: [-f config-file] [file]\n",
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000331 argv[0]);
332 break;
333 }
334 }
335 if (optind < argc) {
336 in = fopen(argv[optind], "r");
337 if (!in) {
338 perror(argv[optind]);
339 exit(1);
340 }
341 optind++;
342 } else
343 in = stdin;
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400344
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000345 if (optind < argc) {
346 outfn = argv[optind];
Theodore Ts'o45d21611998-01-19 14:40:24 +0000347 newfn = (char *) malloc(strlen(outfn)+20);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000348 if (!newfn) {
349 fprintf(stderr, "Memory error! Exiting.\n");
350 exit(1);
351 }
352 strcpy(newfn, outfn);
353 strcat(newfn, ".new");
354 out = fopen(newfn, "w");
355 if (!out) {
356 perror(newfn);
357 exit(1);
358 }
359 } else {
360 out = stdout;
361 outfn = 0;
362 }
Theodore Ts'oefc6f622008-08-27 23:07:54 -0400363
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000364 while (!feof(in)) {
365 if (fgets(line, sizeof(line), in) == NULL)
366 break;
367 substitute_line(line);
368 fputs(line, out);
369 }
370 fclose(in);
371 fclose(out);
372 if (outfn) {
Andreas Dilger3f5ef962006-08-05 14:41:00 -0400373 struct stat st;
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000374 if (compare_file(outfn, newfn)) {
375 if (verbose)
376 printf("No change, keeping %s.\n", outfn);
Theodore Ts'odd607052004-04-03 13:53:46 -0500377 if (adjust_timestamp) {
378 if (stat(outfn, &stbuf) == 0) {
379 if (verbose)
380 printf("Updating modtime for %s\n", outfn);
381 ut.actime = stbuf.st_atime;
382 ut.modtime = time(0);
383 if (utime(outfn, &ut) < 0)
384 perror("utime");
385 }
386 }
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000387 unlink(newfn);
388 } else {
389 if (verbose)
390 printf("Creating or replacing %s.\n", outfn);
391 rename(newfn, outfn);
392 }
Andreas Dilger3f5ef962006-08-05 14:41:00 -0400393 /* set read-only to alert user it is a generated file */
394 if (stat(outfn, &st) == 0)
395 chmod(outfn, st.st_mode & ~0222);
Theodore Ts'o44339bd1997-10-15 02:47:20 +0000396 }
397 return (0);
398}
399
400