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 struct subst_entry *subst_table = 0; 34 35 static int add_subst(char *name, char *value) 36 { 37 struct subst_entry *ent = 0; 38 int retval; 39 40 retval = ENOMEM; 41 ent = (struct subst_entry *) malloc(sizeof(struct subst_entry)); 42 if (!ent) 43 goto fail; 44 ent->name = (char *) malloc(strlen(name)+1); 45 if (!ent->name) 46 goto fail; 47 ent->value = (char *) malloc(strlen(value)+1); 48 if (!ent->value) 49 goto fail; 50 strcpy(ent->name, name); 51 strcpy(ent->value, value); 52 ent->next = subst_table; 53 subst_table = ent; 54 return 0; 55 fail: 56 if (ent) { 57 if (ent->name) 58 free(ent->name); 59 if (ent->value) 60 free(ent->value); 61 free(ent); 62 } 63 return retval; 64 } 65 66 static struct subst_entry *fetch_subst_entry(char *name) 67 { 68 struct subst_entry *ent; 69 70 for (ent = subst_table; ent; ent = ent->next) { 71 if (strcmp(name, ent->name) == 0) 72 break; 73 } 74 return ent; 75 } 76 77 /* 78 * Given the starting and ending position of the replacement name, 79 * check to see if it is valid, and pull it out if it is. 80 */ 81 static char *get_subst_symbol(const char *begin, size_t len, char prefix) 82 { 83 static char replace_name[128]; 84 char *cp, *start; 85 86 start = replace_name; 87 if (prefix) 88 *start++ = prefix; 89 90 if (len > sizeof(replace_name)-2) 91 return NULL; 92 memcpy(start, begin, len); 93 start[len] = 0; 94 95 /* 96 * The substitution variable must all be in the of [0-9A-Za-z_]. 97 * If it isn't, this must be an invalid symbol name. 98 */ 99 for (cp = start; *cp; cp++) { 100 if (!(*cp >= 'a' && *cp <= 'z') && 101 !(*cp >= 'A' && *cp <= 'Z') && 102 !(*cp >= '0' && *cp <= '9') && 103 !(*cp == '_')) 104 return NULL; 105 } 106 return (replace_name); 107 } 108 109 static void replace_string(char *begin, char *end, char *newstr) 110 { 111 int replace_len, len; 112 113 replace_len = strlen(newstr); 114 len = end - begin; 115 if (replace_len == 0) 116 memmove(begin, end+1, strlen(end)+1); 117 else if (replace_len != len+1) 118 memmove(end+(replace_len-len-1), end, 119 strlen(end)+1); 120 memcpy(begin, newstr, replace_len); 121 } 122 123 static void substitute_line(char *line) 124 { 125 char *ptr, *name_ptr, *end_ptr; 126 struct subst_entry *ent; 127 char *replace_name; 128 size_t len; 129 130 /* 131 * Expand all @FOO@ substitutions 132 */ 133 ptr = line; 134 while (ptr) { 135 name_ptr = strchr(ptr, '@'); 136 if (!name_ptr) 137 break; /* No more */ 138 if (*(++name_ptr) == '@') { 139 /* 140 * Handle tytso@@mit.edu --> tytso (at) mit.edu 141 */ 142 memmove(name_ptr-1, name_ptr, strlen(name_ptr)+1); 143 ptr = name_ptr+1; 144 continue; 145 } 146 end_ptr = strchr(name_ptr, '@'); 147 if (!end_ptr) 148 break; 149 len = end_ptr - name_ptr; 150 replace_name = get_subst_symbol(name_ptr, len, 0); 151 if (!replace_name) { 152 ptr = name_ptr; 153 continue; 154 } 155 ent = fetch_subst_entry(replace_name); 156 if (!ent) { 157 fprintf(stderr, "Unfound expansion: '%s'\n", 158 replace_name); 159 ptr = end_ptr + 1; 160 continue; 161 } 162 #if 0 163 fprintf(stderr, "Replace name = '%s' with '%s'\n", 164 replace_name, ent->value); 165 #endif 166 ptr = name_ptr-1; 167 replace_string(ptr, end_ptr, ent->value); 168 if ((ent->value[0] == '@') && 169 (strlen(replace_name) == strlen(ent->value)-2) && 170 !strncmp(replace_name, ent->value+1, 171 strlen(ent->value)-2)) 172 /* avoid an infinite loop */ 173 ptr += strlen(ent->value); 174 } 175 /* 176 * Now do a second pass to expand ${FOO} 177 */ 178 ptr = line; 179 while (ptr) { 180 name_ptr = strchr(ptr, '$'); 181 if (!name_ptr) 182 break; /* No more */ 183 if (*(++name_ptr) != '{') { 184 ptr = name_ptr; 185 continue; 186 } 187 name_ptr++; 188 end_ptr = strchr(name_ptr, '}'); 189 if (!end_ptr) 190 break; 191 len = end_ptr - name_ptr; 192 replace_name = get_subst_symbol(name_ptr, len, '$'); 193 if (!replace_name) { 194 ptr = name_ptr; 195 continue; 196 } 197 ent = fetch_subst_entry(replace_name); 198 if (!ent) { 199 ptr = end_ptr + 1; 200 continue; 201 } 202 #if 0 203 fprintf(stderr, "Replace name = '%s' with '%s'\n", 204 replace_name, ent->value); 205 #endif 206 ptr = name_ptr-2; 207 replace_string(ptr, end_ptr, ent->value); 208 } 209 } 210 211 static void parse_config_file(FILE *f) 212 { 213 char line[2048]; 214 char *cp, *ptr; 215 216 while (!feof(f)) { 217 memset(line, 0, sizeof(line)); 218 if (fgets(line, sizeof(line), f) == NULL) 219 break; 220 /* 221 * Strip newlines and comments. 222 */ 223 cp = strchr(line, '\n'); 224 if (cp) 225 *cp = 0; 226 cp = strchr(line, '#'); 227 if (cp) 228 *cp = 0; 229 /* 230 * Skip trailing and leading whitespace 231 */ 232 for (cp = line + strlen(line) - 1; cp >= line; cp--) { 233 if (*cp == ' ' || *cp == '\t') 234 *cp = 0; 235 else 236 break; 237 } 238 cp = line; 239 while (*cp && isspace(*cp)) 240 cp++; 241 ptr = cp; 242 /* 243 * Skip empty lines 244 */ 245 if (*ptr == 0) 246 continue; 247 /* 248 * Ignore future extensions 249 */ 250 if (*ptr == '@') 251 continue; 252 /* 253 * Parse substitutions 254 */ 255 for (cp = ptr; *cp; cp++) 256 if (isspace(*cp)) 257 break; 258 *cp = 0; 259 for (cp++; *cp; cp++) 260 if (!isspace(*cp)) 261 break; 262 #if 0 263 printf("Substitute: '%s' for '%s'\n", ptr, cp ? cp : "<NULL>"); 264 #endif 265 add_subst(ptr, cp); 266 } 267 } 268 269 /* 270 * Return 0 if the files are different, 1 if the files are the same. 271 */ 272 static int compare_file(const char *outfn, const char *newfn) 273 { 274 FILE *old_f, *new_f; 275 char oldbuf[2048], newbuf[2048], *oldcp, *newcp; 276 int retval; 277 278 old_f = fopen(outfn, "r"); 279 if (!old_f) 280 return 0; 281 new_f = fopen(newfn, "r"); 282 if (!new_f) { 283 fclose(old_f); 284 return 0; 285 } 286 287 while (1) { 288 oldcp = fgets(oldbuf, sizeof(oldbuf), old_f); 289 newcp = fgets(newbuf, sizeof(newbuf), new_f); 290 if (!oldcp && !newcp) { 291 retval = 1; 292 break; 293 } 294 if (!oldcp || !newcp || strcmp(oldbuf, newbuf)) { 295 retval = 0; 296 break; 297 } 298 } 299 fclose(old_f); 300 fclose(new_f); 301 return retval; 302 } 303 304 305 306 int main(int argc, char **argv) 307 { 308 char line[2048]; 309 int c; 310 FILE *in, *out; 311 char *outfn = NULL, *newfn = NULL; 312 int verbose = 0; 313 int adjust_timestamp = 0; 314 struct stat stbuf; 315 struct utimbuf ut; 316 317 while ((c = getopt (argc, argv, "f:tv")) != EOF) { 318 switch (c) { 319 case 'f': 320 in = fopen(optarg, "r"); 321 if (!in) { 322 perror(optarg); 323 exit(1); 324 } 325 parse_config_file(in); 326 fclose(in); 327 break; 328 case 't': 329 adjust_timestamp++; 330 break; 331 case 'v': 332 verbose++; 333 break; 334 default: 335 fprintf(stderr, "%s: [-f config-file] [file]\n", 336 argv[0]); 337 break; 338 } 339 } 340 if (optind < argc) { 341 in = fopen(argv[optind], "r"); 342 if (!in) { 343 perror(argv[optind]); 344 exit(1); 345 } 346 optind++; 347 } else 348 in = stdin; 349 350 if (optind < argc) { 351 outfn = argv[optind]; 352 newfn = (char *) malloc(strlen(outfn)+20); 353 if (!newfn) { 354 fprintf(stderr, "Memory error! Exiting.\n"); 355 exit(1); 356 } 357 strcpy(newfn, outfn); 358 strcat(newfn, ".new"); 359 out = fopen(newfn, "w"); 360 if (!out) { 361 perror(newfn); 362 exit(1); 363 } 364 } else { 365 out = stdout; 366 outfn = 0; 367 } 368 369 while (!feof(in)) { 370 if (fgets(line, sizeof(line), in) == NULL) 371 break; 372 substitute_line(line); 373 fputs(line, out); 374 } 375 fclose(in); 376 fclose(out); 377 if (outfn) { 378 struct stat st; 379 if (compare_file(outfn, newfn)) { 380 if (verbose) 381 printf("No change, keeping %s.\n", outfn); 382 if (adjust_timestamp) { 383 if (stat(outfn, &stbuf) == 0) { 384 if (verbose) 385 printf("Updating modtime for %s\n", outfn); 386 ut.actime = stbuf.st_atime; 387 ut.modtime = time(0); 388 if (utime(outfn, &ut) < 0) 389 perror("utime"); 390 } 391 } 392 unlink(newfn); 393 } else { 394 if (verbose) 395 printf("Creating or replacing %s.\n", outfn); 396 rename(newfn, outfn); 397 } 398 /* set read-only to alert user it is a generated file */ 399 if (stat(outfn, &st) == 0) 400 chmod(outfn, st.st_mode & ~0222); 401 } 402 return (0); 403 } 404 405 406