/* $Revision: 1.1.1.1 $ ** ** A C version of Henry Spencer's "subst" script. */ #include #include #include #define LINESIZE 1024 #define FNAMESIZE 1024 #define PARAMSIZE 128 #define WHITE(c) ((c) == ' ' || (c) == '\t') /* ** AFS doesn't support hard links, so enable this #define. #define USE_RENAME */ /* ** If you don't have getopt in your C library, enable this #define. #define NEED_GETOPT */ typedef struct _PAIR { char *Name; int Length; char *Value; } PAIR; static char *argv0; extern char *optarg; extern int optind; extern void exit(); extern char *malloc(); extern char *strcpy(); /* ** Local implementations of common C library functions. */ /* ** Return string represtation of errno. */ static char * xstrerror() { extern int sys_nerr; extern char *sys_errlist[]; extern int errno; static char buff[30]; if (errno >= 0 && errno < sys_nerr) return sys_errlist[errno]; (void)sprintf(buff, "Error code %d\n", errno); return buff; } /* ** Return the first occurrence of 'c' in 'p' or NULL if not there. */ static char * xstrchr(p, c) register char *p; register char c; { for ( ; *p; p++) if (*p == c) return p; return NULL; } /* ** Return the last occurrence of 'c' in 'p' or NULL if not there. */ static char * xstrrchr(p, c) register char *p; register char c; { register char *ret; for (ret = NULL; *p; p++) if (*p == c) ret = p; return ret; } /* ** Copy a string to malloc'd memory or exit. */ static char * xstrdup(p) char *p; { char *new; if ((new = malloc(strlen(p) + 1)) == NULL) { (void)fprintf(stderr, "%s: Can't copy \"%s\", %s\n", argv0, p, xstrerror()); exit(1); } return strcpy(new, p); } #if defined(NEED_GETOPT) #define TYPE int #define ERR(s, c) \ if (opterr) { \ char buff[2]; \ buff[0] = c; buff[1] = '\n'; \ (void)write(2, av[0], (TYPE)strlen(av[0])); \ (void)write(2, s, (TYPE)strlen(s)); \ (void)write(2, buff, 2); \ } int opterr = 1; int optind = 1; int optopt; char *optarg; /* ** Return options and their values from the command line. ** This comes from the AT&T public-domain getopt published in mod.sources ** (i.e., comp.sources.unix before the great Usenet renaming). */ int getopt(ac, av, opts) int ac; char *av[]; char *opts; { static int i = 1; char *p; /* Move to next value from argv? */ if (i == 1) { if (optind >= ac || av[optind][0] != '-' || av[optind][1] == '\0') return EOF; if (strcmp(av[optind], "--") == 0) { optind++; return EOF; } } /* Get next option character. */ if ((optopt = av[optind][i]) == ':' || (p = IDX(opts, optopt)) == NULL) { ERR(": illegal option -- ", optopt); if (av[optind][++i] == '\0') { optind++; i = 1; } return '?'; } /* Snarf argument? */ if (*++p == ':') { if (av[optind][i + 1] != '\0') optarg = &av[optind++][i + 1]; else { if (++optind >= ac) { ERR(": option requires an argument -- ", optopt); i = 1; return '?'; } optarg = av[optind++]; } i = 1; } else { if (av[optind][++i] == '\0') { i = 1; optind++; } optarg = NULL; } return optopt; } #endif /* defined(NEED_GETOPT) */ /* ** Simulate "mv $from $to" -- return no useful status. We know that ** the $from and $to are on the same filesystem. */ static void mv(from, to) char *from; char *to; { if (unlink(to) < 0 && errno != ENOENT) { (void)fprintf(stderr, "%s: Can't unlink %s, %s\n", argv0, to, xstrerror()); return; } #if defined(USE_RENAME) if (rename(from, to) < 0) { (void)fprintf(stderr, "%s: Can't rename %s to %s, %s\n", argv0, from, to, xstrerror()); return; } #else if (link(from, to) < 0) { (void)fprintf(stderr, "%s: Can't link %s to %s, %s\n", argv0, from, to, xstrerror()); return; } if (unlink(from) < 0) (void)fprintf(stderr, "%s: Can't unlink %s, %s\n", argv0, from, xstrerror()); #endif /* defined(USE_RENAME) */ } /* ** Simulate "cmp -s $n1 $n2" -- return 0 if files are the same. */ static int cmp(n1, n2) char *n1; char *n2; { FILE *f1; FILE *f2; int c; if ((f1 = fopen(n1, "r")) == NULL) return 1; if ((f2 = fopen(n2, "r")) == NULL) { (void)fclose(f1); return 1; } while ((c = getc(f1)) != EOF) if (getc(f2) != c) { (void)fclose(f1); (void)fclose(f2); return 1; } if (getc(f2) != EOF) { (void)fclose(f1); (void)fclose(f2); return 1; } (void)fclose(f1); (void)fclose(f2); return 0; } /* ** If line does not look like a template, return NULL, otherwise modify ** it to delete the trailing gunk and return the start of the template. */ static char * istemplate(line) char *line; { char *p; char *start; /* Find "=()<" and remember where it starts. */ for (p = line; (p = xstrchr(p, '=')) != NULL; p++) if (p[1] == '(' && p[2] == ')' && p[3] == '<') break; if (p == NULL) return NULL; start = &p[4]; /* Now find ">()=" and nip it off. */ for (p = start; (p = xstrchr(p, '>')) != NULL; p++) if (p[1] == '(' && p[2] == ')' && p[3] == '=') { *p++ = '\n'; *p = '\0'; return start; } return NULL; } /* ** Splice three strings together, returning an allocated copy. */ static char * splice(s1, s2, s3) char *s1; char *s2; char *s3; { int i; char *new; i = strlen(s1) + strlen(s2) + strlen(s3) + 1; if ((new = malloc(i)) == NULL) { (void)fprintf(stderr, "%s: Can't splice %s+%s+%s, %s\n", argv0, s1, s2, s3, xstrerror()); exit(1); } (void)sprintf(new, "%s%s%s", s1, s2, s3); return new; } /* ** Substitute all found patterns in the line and print it. Using the goto ** makes the code more clear than using do/while. */ static int doline(f, out, line, tp, end) char *f; FILE *out; char *line; PAIR *tp; PAIR *end; { char *p; char *new; char save; int count; for (count = 0, line = xstrdup(line); tp < end; tp++) { Again: for (p = line; (p = xstrchr(p, tp->Name[0])) != NULL; p++) if (strncmp(p, tp->Name, tp->Length) == 0) { save = *p; *p = '\0'; count++; new = splice(line, tp->Value, p + tp->Length); *p = save; if (strcmp(new, line) == 0) { (void)fprintf(stderr, "%s: subst loop in %s:\n\t%s\n", argv0, f, line); free(new); break; } free(line); line = new; goto Again; } } if (count > 0 && fputs(line, out) == EOF) { (void)fprintf(stderr, "%s: can't write %s, %s\n", argv0, f, xstrerror()); free(line); return -1; } free(line); return count; } /* ** Process one file, carefully substituting it in place. */ static void Process(f, Table, end) char *f; PAIR *Table; PAIR *end; { char new[FNAMESIZE]; char old[FNAMESIZE]; char line[LINESIZE]; int bad; int i; int count; FILE *in; FILE *out; FILE *temp; char *p; /* First, figure out temporary names. */ if ((p = xstrrchr(f, '/')) == NULL) { (void)strcpy(new, "substtmp.new"); (void)strcpy(old, "substtmp.old"); } else { *p = '\0'; (void)sprintf(new, "%s/substtmp.new", f); (void)sprintf(old, "%s/substtmp.old", f); *p = '/'; } /* Test existences. */ if ((in = fopen(f, "r")) == NULL) { (void)fprintf(stderr, "%s: can't open %s, %s\n", argv0, f, xstrerror()); return; } if ((temp = fopen(new, "r")) != NULL) { (void)fclose(in); (void)fprintf(stderr, "%s: %s exists, cannot proceed\n", argv0, new); exit(1); } if ((temp = fopen(old, "r")) != NULL) { (void)fprintf(stderr, "%s: %s exists, cannot proceed\n", argv0, old); exit(1); } temp = fopen(old, "w"); out = fopen(new, "w"); if (out == NULL || temp == NULL) { if (temp != NULL) (void)fclose(temp); (void)unlink(old); if (out != NULL) (void)fclose(out); (void)unlink(new); (void)fprintf(stderr, "%s: cannot create temporaries %s and %s\n", argv0, old, new); exit(1); } (void)fclose(temp); /* Generate the new version. */ for (i = 1, bad = 0; fgets(line, sizeof line, in) != NULL; i++) { if ((p = xstrchr(line, '\n')) == NULL) { (void)fprintf(stderr, "%s: Line %d of %s is too long (or doesn't end with a newline)\n", argv0, i, f); bad++; break; } (void)fputs(line, out); if ((p = istemplate(line)) != NULL) { if ((count = doline(f, out, p, Table, end)) < 0) { bad++; break; } if (count > 0) { (void)fgets(line, sizeof line, in); i++; } else (void)fprintf(stderr, "%s: %s:%d unknown parameter or bad line:\n\t%s", argv0, f, i, p); } } (void)fclose(in); if (fflush(out) == EOF || fclose(out) == EOF) { (void)fprintf(stderr, "%s: can't close %s, %s\n", argv0, f, xstrerror()); bad++; } if (bad || cmp(new, f) == 0) { (void)unlink(old); (void)unlink(new); (void)printf("%s: unchanged\n", f); return; } /* Substitute new for old the only safe way -- ignore signals. */ (void)signal(SIGHUP, SIG_IGN); (void)signal(SIGINT, SIG_IGN); (void)signal(SIGTERM, SIG_IGN); mv(f, old); mv(new, f); (void)signal(SIGHUP, SIG_DFL); (void)signal(SIGINT, SIG_DFL); (void)signal(SIGTERM, SIG_DFL); (void)printf("%s: updated\n", f); (void)unlink(old); } /* ** Print usage message and exit. */ static void Usage() { (void)fprintf(stderr, "Usage: %s -f file victims...\n", argv0); exit(1); } int main(ac, av) int ac; char *av[]; { static char NIL[] = ""; char *ctlfile; char *p; char *dest; FILE *F; int i; char buff[LINESIZE]; char name[PARAMSIZE]; PAIR *Table; PAIR *tp; /* Set defaults. */ ctlfile = NULL; argv0 = av[0]; /* Parse JCL. */ while ((i = getopt(ac, av, "f:")) != EOF) switch (i) { default: Usage(); /* NOTREACHED */ case 'f': if (ctlfile != NULL) Usage(); ctlfile = optarg; break; } ac -= optind; av += optind; /* Open control file, count lines, allocate table. */ if ((F = fopen(ctlfile, "r")) == NULL) { (void)fprintf(stderr, "%s: Can't open %s to read it, %s\n", argv0, ctlfile, xstrerror()); exit(1); } for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++) continue; if ((Table = (PAIR *)malloc(i * sizeof *Table)) == NULL) { (void)fprintf(stderr, "%s: Can't allocate %d table elements, %s\n", argv0, i, xstrerror()); exit(1); } /* Now parse the table. */ (void)fseek(F, 0L, 0); for (i = 1, tp = Table; fgets(buff, sizeof buff, F) != NULL; i++) { if ((p = xstrchr(buff, '\n')) == NULL) { (void)fprintf(stderr, "%s: Line %d of %s is too long\n", argv0, i, ctlfile); exit(1); } *p = '\0'; /* Skip empty lines, comment lines, and all-blank lines. */ if (buff[0] == '\0' || buff[0] == '#') continue; for (p = buff; WHITE(*p); p++) continue; if (*p == '\0') continue; /* Find end of first word, copy second word (or empty string) */ for (p = buff; *p && !WHITE(*p); p++) continue; if (*p == '\0') tp->Value = NIL; else { for (*p++ = '\0'; *p && WHITE(*p); p++) continue; tp->Value = xstrdup(p); /* Turn things like \& into &. */ for (p = dest = tp->Value; *p; p++) *dest++ = (*p == '\\' && p[1] != '\0') ? *++p : *p; *dest = '\0'; } /* Turn first word into something directly searchable. */ if (strlen(buff) > sizeof name - 4) { (void)fprintf(stderr, "%s: Parameter %s is too long\n", argv0, buff); exit(1); } (void)sprintf(name, "@<%s>@", buff); tp->Name = xstrdup(name); tp->Length = strlen(tp->Name); tp++; } (void)fclose(F); while (*av != NULL) Process(*av++, Table, tp); exit(0); /* NOTREACHED */ }