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