592 lines
12 KiB
C
592 lines
12 KiB
C
/* This is from INN. */
|
||
|
||
/* $Revision: 1.2 $
|
||
**
|
||
** 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();
|
||
|
||
|
||
|
||
/*
|
||
** Local implementations of common C library functions.
|
||
*/
|
||
|
||
/*
|
||
** Return string represtation of errno.
|
||
*/
|
||
static char *
|
||
xstrerror()
|
||
{
|
||
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;
|
||
|
||
count = 0;
|
||
for (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\n",
|
||
argv0, i, f);
|
||
bad++;
|
||
break;
|
||
}
|
||
(void)fputs(line, out);
|
||
if ((p = istemplate(line)) != NULL) {
|
||
count = doline(f, out, p, Table, end);
|
||
if (count < 0) {
|
||
bad++;
|
||
break;
|
||
}
|
||
if(count > 0) {
|
||
(void)fgets(line, sizeof line, in);
|
||
i++;
|
||
}
|
||
else
|
||
fprintf(stderr,"%s: %s:%d: unknown variable:\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 */
|
||
}
|