+/* $Revision: 1.12 $
+**
+** A C version of Henry Spencer's "subst" script.
+*/
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+
+#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();
+
+\f
+
+/*
+** Local implementations of common C library functions.
+*/
+
+/*
+** Return string represtation of errno.
+*/
+static char *
+xstrerror()
+{
+ extern char *strerror();
+
+ return strerror(errno);
+}
+
+
+/*
+** Return the first occurrence of 'c' in 'p' or NULL if not there.
+*/
+static char *
+xstrchr(p, c)
+ register char *p;
+ register char c;
+{
+ extern char *strchr();
+
+ return strchr(p, c);
+}
+
+
+/*
+** Return the last occurrence of 'c' in 'p' or NULL if not there.
+*/
+static char *
+xstrrchr(p, c)
+ register char *p;
+ register char c;
+{
+ extern char *strrchr();
+
+ return strrchr(p, c);
+}
+
+
+/*
+** 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) */
+
+\f
+
+/*
+** 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 */
+}