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