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