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