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