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 #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 (at) 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 
    464