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