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 static struct subst_entry *subst_table = 0;
     34 
     35 static int add_subst(char *name, char *value)
     36 {
     37 	struct subst_entry	*ent = 0;
     38 
     39 	ent = (struct subst_entry *) malloc(sizeof(struct subst_entry));
     40 	if (!ent)
     41 		goto fail;
     42 	ent->name = (char *) malloc(strlen(name)+1);
     43 	if (!ent->name)
     44 		goto fail;
     45 	ent->value = (char *) malloc(strlen(value)+1);
     46 	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;
     53 fail:
     54 	if (ent) {
     55 		free(ent->name);
     56 		free(ent);
     57 	}
     58 	return ENOMEM;
     59 }
     60 
     61 static 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 
     72 /*
     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  */
     76 static char *get_subst_symbol(const char *begin, size_t len, char prefix)
     77 {
     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;
     89 
     90 	/*
     91 	 * The substitution variable must all be in the of [0-9A-Za-z_].
     92 	 * 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') &&
     97 		    !(*cp >= '0' && *cp <= '9') &&
     98 		    !(*cp == '_'))
     99 			return NULL;
    100 	}
    101 	return (replace_name);
    102 }
    103 
    104 static 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;
    110 	if (replace_len == 0)
    111 		memmove(begin, end+1, strlen(end)+1);
    112 	else if (replace_len != len+1)
    113 		memmove(end+(replace_len-len-1), end,
    114 			strlen(end)+1);
    115 	memcpy(begin, newstr, replace_len);
    116 }
    117 
    118 static void substitute_line(char *line)
    119 {
    120 	char	*ptr, *name_ptr, *end_ptr;
    121 	struct subst_entry *ent;
    122 	char	*replace_name;
    123 	size_t	len;
    124 
    125 	/*
    126 	 * Expand all @FOO@ substitutions
    127 	 */
    128 	ptr = line;
    129 	while (ptr) {
    130 		name_ptr = strchr(ptr, '@');
    131 		if (!name_ptr)
    132 			break;	/* No more */
    133 		if (*(++name_ptr) == '@') {
    134 			/*
    135 			 * Handle tytso@@mit.edu --> tytso (at) 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, '@');
    142 		if (!end_ptr)
    143 			break;
    144 		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 		}
    150 		ent = fetch_subst_entry(replace_name);
    151 		if (!ent) {
    152 			fprintf(stderr, "Unfound expansion: '%s'\n",
    153 				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
    161 		ptr = name_ptr-1;
    162 		replace_string(ptr, end_ptr, ent->value);
    163 		if ((ent->value[0] == '@') &&
    164 		    (strlen(replace_name) == strlen(ent->value)-2) &&
    165 		    !strncmp(replace_name, ent->value+1,
    166 			     strlen(ent->value)-2))
    167 			/* avoid an infinite loop */
    168 			ptr += strlen(ent->value);
    169 	}
    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;
    191 		}
    192 		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);
    203 	}
    204 }
    205 
    206 static 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 		 */
    245 		if (*ptr == '@')
    246 			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
    258 		printf("Substitute: '%s' for '%s'\n", ptr, cp ? cp : "<NULL>");
    259 #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  */
    267 static int compare_file(const char *outfn, const char *newfn)
    268 {
    269 	FILE	*old_f, *new_f;
    270 	char	oldbuf[2048], newbuf[2048], *oldcp, *newcp;
    271 	int	retval;
    272 
    273 	old_f = fopen(outfn, "r");
    274 	if (!old_f)
    275 		return 0;
    276 	new_f = fopen(newfn, "r");
    277 	if (!new_f) {
    278 		fclose(old_f);
    279 		return 0;
    280 	}
    281 
    282 	while (1) {
    283 		oldcp = fgets(oldbuf, sizeof(oldbuf), old_f);
    284 		newcp = fgets(newbuf, sizeof(newbuf), new_f);
    285 		if (!oldcp && !newcp) {
    286 			retval = 1;
    287 			break;
    288 		}
    289 		if (!oldcp || !newcp || strcmp(oldbuf, newbuf)) {
    290 			retval = 0;
    291 			break;
    292 		}
    293 	}
    294 	fclose(old_f);
    295 	fclose(new_f);
    296 	return retval;
    297 }
    298 
    299 
    300 
    301 int 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;
    308 	int	adjust_timestamp = 0;
    309 	struct stat stbuf;
    310 	struct utimbuf ut;
    311 
    312 	while ((c = getopt (argc, argv, "f:tv")) != EOF) {
    313 		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;
    323 		case 't':
    324 			adjust_timestamp++;
    325 			break;
    326 		case 'v':
    327 			verbose++;
    328 			break;
    329 		default:
    330 			fprintf(stderr, "%s: [-f config-file] [file]\n",
    331 				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;
    344 
    345 	if (optind < argc) {
    346 		outfn = argv[optind];
    347 		newfn = (char *) malloc(strlen(outfn)+20);
    348 		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 	}
    363 
    364 	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) {
    373 		struct stat st;
    374 		if (compare_file(outfn, newfn)) {
    375 			if (verbose)
    376 				printf("No change, keeping %s.\n", outfn);
    377 			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 			}
    387 			unlink(newfn);
    388 		} else {
    389 			if (verbose)
    390 				printf("Creating or replacing %s.\n", outfn);
    391 			rename(newfn, outfn);
    392 		}
    393 		/* set read-only to alert user it is a generated file */
    394 		if (stat(outfn, &st) == 0)
    395 			chmod(outfn, st.st_mode & ~0222);
    396 	}
    397 	return (0);
    398 }
    399 
    400 
    401