
/* Gated Release 3.5 */
/* Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.  All */
/* rights reserved.  Refer to Particulars and other Copyright notices at */
/* the end of this file.  */
/*  */

#include <sys/param.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>

/* Our versions of INSQUE/REMQUE */

/* The structure */
typedef struct _qelement {
    struct _qelement *q_forw;
    struct _qelement *q_back;
} *qelement;


#ifndef	INSQUE
/* Define INSQUE/REMQUE if not already defined (as maybe assembler code) */
#define INSQUE(elem, pred) { \
				 register qelement Xe = (qelement) (elem); \
				 register qelement Xp = (qelement) (pred); \
				 Xp->q_forw = (Xe->q_forw = (Xe->q_back = Xp)->q_forw)->q_back = Xe; \
			      }

#define	REMQUE(elem)	{ \
    			     register qelement Xe = (qelement) elem; \
			     (Xe->q_back->q_forw = Xe->q_forw)->q_back = Xe->q_back; \
			 }
#endif	/* INSQUE */


int main(int argc, char **argv);

static int verbose;
static char *my_name;

struct prefix {
    struct prefix *forw;
    struct prefix *back;
    char *prefix;
    int wild;
    size_t len;
} ;

static struct prefix prefixes = { &prefixes, &prefixes };

#define	PFX_LIST(pfx)	for (pfx = prefixes.forw; \
			     pfx != &prefixes; \
			     pfx = pfx->forw)
#define	PFX_LIST_END(pfx)

static struct prefix alls = { &alls, &alls };

#define	ALL_LIST(pfx)	for (pfx = alls.forw; \
			     pfx != &alls; \
			     pfx = pfx->forw)
#define	ALL_LIST_END(pfx)


struct var {
    struct var *var_forw;
    struct var *var_back;
    off_t var_size;
    char var_name[MAXNAMLEN + 1];
    char var_data[0];
} ;

static struct var vars = { &vars, &vars };

#define	VAR_LIST(var)	for (var = vars.var_forw; \
				     var != &vars; \
				     var = var->var_forw)
#define	VAR_LIST_END(var)


#define	SUFFIX_SAVE	".save"
#define	SUFFIX_NEW	".new"

static int
write_variable(int fd,
	       struct var *var,
	       char *prefix,
	       int plen,
	       char *suffix,
	       int slen)
{
    register char *cp = var->var_data;
    char *limit = cp + var->var_size;
    char *line = cp;

    do {
	if (*cp == '\n') {
	    if (write(fd, prefix, plen) < 0) {
		perror("write");
		return 1;
	    }
	    if (write(fd, line, cp - line) < 0) {
		perror("write");
		return 1;
	    }
	    if (write(fd, suffix, slen) < 0) {
		perror("write");
		return 1;
	    }
	    line = cp + 1;
	}
    } while (cp++ < limit) ;

    if (line < limit) {
	if (write(fd, prefix, plen) < 0) {
	    perror("write");
	    return 1;
	}
	if (write(fd, line, cp - line) < 0) {
	    perror("write");
	    return 1;
	}
	if (write(fd, suffix, slen) < 0) {
	    perror("write");
	    return 1;
	}
    }

    return 0;
}

static int
edit_file(char *path)
{
    int in_fd = -1;
    int out_fd = -1;
    int cc;
    int ret = 0;
    char *buf;
    char path_new[MAXNAMLEN + 1];
    char path_save[MAXNAMLEN + 1];
    struct stat statbuf;

    if (verbose) {
	fprintf(stderr, "%s: processing %s\n",
		my_name,
		path);
    }
    
    /* Make file names */
    strcpy(path_new, path);
    strcat(path_new, SUFFIX_NEW);

    strcpy(path_save, path);
    strcat(path_save, SUFFIX_SAVE);

    /* Check to make sure temp files don't exist */
    if (access(path_save, F_OK) == 0) {
	fprintf(stderr, "%s: save file %s exists\n",
		my_name,
		path_save);
	goto Close;
    }
    if (access(path_new, F_OK) == 0) {
	fprintf(stderr, "%s: new file %s exists\n",
		my_name,
		path_new);
	goto Close;
    }

    /* Open the input file */
    in_fd = open(path, O_RDONLY);
    if (in_fd < 0) {
	(void) fprintf(stderr, "%s: open(%s)",
		       my_name,
		       path);
	perror("");
	goto Close;
    }

    if (fstat(in_fd, &statbuf) < 0) {
	perror("fstat");
	goto Close;
    }

    if (!S_ISREG(statbuf.st_mode)) {
	(void) fprintf(stderr, "%s: not a file: %s\n",
		       my_name,
		       path);
	goto Close;
    }

    /* Open the output file */
    out_fd = open(path_new, O_WRONLY|O_CREAT|O_EXCL, statbuf.st_mode);
    if (out_fd < 0) {
	(void) fprintf(stderr, "%s: open(%s)",
		       my_name,
		       path_new);
	perror("");
	goto Close;
    }

    /* Allocate the buffer and read the file */
    buf = alloca((size_t) statbuf.st_size);
    assert(buf);

#ifdef	notdef
    printf("%s: reading %s\n",
	   my_name,
	   path);
#endif	/* notdef */

    while ((cc = read(in_fd, buf, statbuf.st_size)) > 0) {
	if (cc != statbuf.st_size) {
	    (void) fprintf(stderr, "%s: %s: truncated read, wanted %u got %u\n",
			   my_name,
			   path,
			   statbuf.st_size,
			   cc);
	    ret++;
	    goto Close;
	}

        {
	    register char *cp = buf;
	    char state = (char) 0;
	    char *line_end = (char *) 0;
	    char *limit = buf + statbuf.st_size;
	    char *line_start = (char) 0;
	    char *var_start = (char *) 0;
	    char *var_end = (char *) 0;
	    char *write_start = cp;

	    do {
	      switch (state) {
	      case 0:
		  /* Line start */
		  line_start = cp;
		  state = '\n';
		  /* Fall through */

	      case '\n':
		  /* Searching for variable */
		  switch (*cp) {
		  case '%':
		      state = *cp;
		      var_start = cp;
		      break;

		  case '\n':
		      line_end = cp;
		      state = (char) 0;
		      break;
		      
		  default:
		      break;
		  }
		  break;

	      case '%':
		  /* Searching for '(' or '#'*/
		  switch (*cp) {
		  case '#':
		  case '(':
		      state = *cp;
		      break;

		  default:
		      state = '\n';
		      break;
		  }
		  break;

	      case '#':
		  /* Searching for '(' */
		  switch (*cp) {
		  case '(':
		      state = *cp;
		      break;

		  default:
		      state = '\n';
		      break;
		  }
		  break;
		  
	      case '(':
		  /* Picking up variable name */
		  switch (*cp) {
		  case ')':
		      var_end = cp;
		      if (*(var_start+1) == '#') {
			  struct var *var;

			  *var_end = (char) 0;
			  var_start += 3;

			  VAR_LIST(var) {
			      if (!strcmp(var->var_name, var_start)) {
				  /* Found variable */

				  /* Write out what we have so far */
				  if (line_start > write_start) {
				      if (write(out_fd,
						write_start,
						line_start - write_start) < 0) {
					  perror("write");
					  ret++;
					  goto Close;
				      }
				  }

				  if (write_variable(out_fd,
						     var,
						     line_start,
						     var_start - line_start - 3,
						     var_end + 1,
						     cp - var_end)) {
				      ret++;
				      goto Close;
				  }
				  write_start = cp + 1;
				  goto Out;
			      }
			  } VAR_LIST_END(var) ;

			  fprintf(stderr, "%s: variable %s not found, file %s\n",
				  my_name,
				  var_start,
				  path);				  

		      Out:
			  *var_end = ')';
			  line_start = var_end + 1;
			  state = '\n';
		      } else {
			  /* Multi-line variable */

			  state = *cp;
		      }
		      break;

		  default:
		      if (!isalnum(*cp)) {
			  state = '\n';
			  break;
		      }
		      /* Fall through */

		  case '.':
		  case '_':
		      break;
		  }
		  break;

	      case ')':
		  /* Searching for newline */
		  switch (*cp) {
		  case '\n':
		      state = (char) 0;

		      {
			  struct var *var;

			  *var_end = (char) 0;
			  var_start += 2;

			  VAR_LIST(var) {
			      if (!strcmp(var->var_name, var_start)) {
				  /* Found variable */

				  /* Write out what we have so far */
				  if (line_start > write_start) {
				      if (write(out_fd,
						write_start,
						line_start - write_start) < 0) {
					  perror("write");
					  ret++;
					  goto Close;
				      }
				  }

				  if (write_variable(out_fd,
						     var,
						     line_start,
						     var_start - line_start - 2,
						     var_end + 1,
						     cp - var_end)) {
				      ret++;
				      goto Close;
				  }
				  write_start = cp + 1;
				  goto Done;
			      }
			  } VAR_LIST_END(var) ;

			  fprintf(stderr, "%s: variable %s not found, file %s\n",
				  my_name,
				  var_start,
				  path);				  

			  *var_end = ')';
		      }

		  Done:
		      line_end = cp;
		      break;

		  default:
		      break;
		  }
		  break;

	      default:
		  abort();
	      }
	    } while (cp++ < limit) ;

	    if (write_start < limit) {
		/* Write remaining buffer */

		if (write(out_fd,
			  write_start,
			  limit - write_start) < 0) {
		    perror("write");
		    ret++;
		    goto Close;
		}
	    }
	}
    }

    if (cc < 0) {
	perror("read");
	ret++;
    }

 Close:
    if (in_fd >= 0
	&& close(in_fd) < 0) {
	perror("close");
	ret++;
    }

    if (out_fd >= 0) {
	if (close(out_fd) < 0) {
	    perror("close");
	    ret++;
	}

	if (!ret) {
	    /* Rename files */

	    /* Rename old to save */
	    if (rename(path, path_save) < 0) {
		perror("rename");
		ret++;
	    }

	    if (!ret) {
		/* Rename new to old */
		if (rename(path_new, path) < 0) {
		    perror("rename");
		    ret++;

		    (void) rename(path_save, path);
		}
	    }
	}

	if (ret) {
	    (void) unlink(path_new);
	}
    }

    return ret;
}


static int
parse_args(int argc,
	   char **argv)
{
    int c;
    int errflg = 0;
    extern char *optarg;

    my_name = (char *) rindex(*argv, '/');
    if (my_name) {
	my_name++;
    } else {
	my_name = *argv;
    }
    
    while ((c = getopt(argc, argv, "A:d:p:P:v")) != -1) {
	int wild = 0;
	
	switch (c) {
	case 'A':
	    {
		size_t len = strlen(optarg);
		struct prefix *pfx = (struct prefix *) malloc(sizeof *pfx + len + sizeof(".ALL") + 1);

		bzero(pfx, sizeof *pfx);
		strcpy(pfx->prefix = (char *) (pfx + 1), optarg);
		strcat(pfx->prefix, ".ALL");
		pfx->len = len;
		INSQUE((struct qelem *) pfx,
		       (struct qelem *) alls.back);
	    }
	    break;
	    
	case 'd':
	    if (chdir(optarg) < 0) {
		perror("chdir");
		errflg++;
	    }
	    break;

	case 'P':
	    wild = 1;
	    /* Fall though */

	case 'p':
	    {
		size_t len = strlen(optarg);
		struct prefix *pfx = (struct prefix *) malloc(sizeof *pfx + len + 1);

		bzero(pfx, sizeof *pfx);
		strcpy(pfx->prefix = (char *) (pfx + 1), optarg);
		pfx->len = len;
		pfx->wild = wild;
		INSQUE((struct qelem *) pfx,
		       (struct qelem *) prefixes.back);
	    }
	    break;
	    
	case 'v':
	    verbose++;
	    break;

	case '?':
	    errflg++;
	    break;
	}
    }
    
    if (errflg) {
	(void) fprintf(stderr, "usage: %s [-v] [-d dir] file1 . . . ",
		      my_name);
	return 0;
    }

    return 1;
}


static int
process_files(int argc,
	      char **argv)
{
    int ret = 0;
    extern int optind;
    struct files {
	struct files *f_next;
	char *f_file;
    } *files = (struct files *) 0;
    register struct files *fp;

    /* Suck up the file names */
    if (optind < argc) {
	/* Process files from the command line */

	for (; optind < argc; optind++) {
	    fp = (struct files *) malloc(sizeof (struct files) + strlen(argv[optind]) + 1);
	    assert(fp);

	    bzero(fp, sizeof *fp);
	    fp->f_file = (char *) (fp + 1);
	    strcpy(fp->f_file, argv[optind]);
	    fp->f_next = files;
	    files = fp;
	}
    } else {
	/* Process files from stdin */
	char inbuf[BUFSIZ];
	
	if (verbose) {
	    fprintf(stderr, "%s: reading file list from stdin\n",
		    my_name);
	}
	
	while (gets(inbuf) == inbuf) {
	    fp = (struct files *) malloc(sizeof (struct files) + strlen(inbuf) + 1);
	    assert(fp);

	    bzero(fp, sizeof *fp);
	    fp->f_file = (char *) (fp + 1);
	    strcpy(fp->f_file, inbuf);
	    fp->f_next = files;
	    files = fp;
	}
    }

    /* Now process the files */
    for (fp = files;
	 fp;
	 fp = fp->f_next) {
	ret += edit_file(fp->f_file);
    }

    return ret;
}


static struct var *
var_alloc(const char *name,
	   off_t size)
{
    struct var *var;

    var = (struct var *) malloc(sizeof *var + size);
    if (!var) {
	return var;
    }
    bzero(var, sizeof *var);
    var->var_forw = var->var_back = (struct var *) 0;
    var->var_size = size;
    strcpy(var->var_name, name);

    return var;
}


static void
var_insert(struct var *new_var)
{
    struct var *var;

    VAR_LIST(var) {
	if (strcmp(var->var_name, new_var->var_name) > 0) {
	    break;
	}
    } VAR_LIST_END(var) ;

    INSQUE(new_var,
	   var->var_back);
}


static struct var *
get_file(const char *path)
{
    int fd;
    const char *name;
    struct var *var = (struct var *) 0;
    struct stat statbuf;

    /* Figure out name */
    if (name = (char *) rindex(path, '/')) {
	name++;
    } else {
	name = path;
    }

    if (stat(path, &statbuf) < 0) {
	(void) fprintf(stderr, "%s: can not stat %s\n",
		       my_name,
		       path);
	return var;
    }

    if (!S_ISREG(statbuf.st_mode)) {
	return var;
    }
    
    fd = open(path, O_RDONLY);
    if (fd < 0) {
	(void) fprintf(stderr, "%s: open(%s)",
		       my_name,
		       path);
	perror("");
	return 0;
    }

    var = var_alloc(name, statbuf.st_size);
    if (!var) {
	return var;
    }

    if (read(fd, var->var_data, statbuf.st_size) != statbuf.st_size) {
	perror("read");
	free(var);
	var = (struct var *) 0;
    }

    /* XXX - Truncate trailing newline? */

    if (close(fd) < 0) {
	perror("close");
    }

    if (var) {
	var_insert(var);
    }

    if (verbose) {
	fprintf(stderr, "%s: read variable %s\n",
		my_name,
		path);
    }
    	    
    return var;
}


static int
dir_read(void)
{
    DIR *dir;
    struct dirent *dirent;
    struct prefix *pfx;

    dir = opendir(".");
    if (!dir) {
	return 0;
    }

    while (dirent = readdir(dir)) {
	PFX_LIST(pfx) {
	    struct var *var;
		
	    if (pfx->wild) {
		if (dirent->d_namlen < pfx->len
		    || !isalpha(dirent->d_name[dirent->d_namlen - 1])
		    || strncmp(dirent->d_name, pfx->prefix, pfx->len)) {
		    continue;
		}
	    } else {
		if (strcmp(dirent->d_name, pfx->prefix)) {
		    continue;
		}
	    }

	    /* Read in the file */
	    var = get_file(dirent->d_name);
	    if (!var) {
		continue;
	    }
	} PFX_LIST_END(pfx) ;
    }

    if (closedir(dir)) {
	perror("closedir");
    }

    return 1;
}


#ifdef	notdef
static void
print_var_list(int size)
{
    struct var *var;
	
    VAR_LIST(var) {
	(void) printf("'%s' size %d\n",
		      var->var_name,
		      var->var_size);
	if (var->var_size <= size) {
	    (void) printf("%s\n",
			  var->var_data);
	}
    } VAR_LIST_END(var) ;

    (void) printf("\n");
}
#endif	/* notdef */


static void
build_all(void)
{
    struct prefix *all;

    ALL_LIST(all) {
	struct var *var;
	struct var *new_var;
	off_t size = 0;

	/* Get copyright info */
	VAR_LIST(var) {
	    if (!strncmp(var->var_name, all->prefix, all->len)
		&& (!var->var_name[all->len]
		    || (var->var_name[all->len] == '.'
			&& isupper(var->var_name[all->len+1])))) {
		size += var->var_size;
	    }
	} VAR_LIST_END(var) ;

	new_var = var_alloc(all->prefix, size);
	new_var->var_size = (off_t) 0;

	VAR_LIST(var) {
	    if (!strncmp(var->var_name, all->prefix, all->len)
		&& (!var->var_name[all->len]
		    || (var->var_name[all->len] == '.'
			&& isupper(var->var_name[all->len+1])))) {
		strcpy(&new_var->var_data[new_var->var_size],
		       var->var_data);
		new_var->var_size += var->var_size;
	    }
	} VAR_LIST_END(var) ;

	var_insert(new_var);
    }
}


int
main(int argc, 
     char **argv)
{
    if (!parse_args(argc, argv)) {
	return 1;
    }

    /* Read in the files */
    if (!dir_read()) {
	return 1;
    }

    /* Build any combined variables */
    build_all();

    return process_files(argc, argv);
}


/*
 * ------------------------------------------------------------------------
 * 
 * 	GateD, Release 3.5
 * 
 * 	Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.
 * 	    All rights reserved.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
 * 	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 * 	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * 	AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * 	Royalty-free licenses to redistribute GateD Release
 * 	3 in whole or in part may be obtained by writing to:
 * 
 * 	    GateDaemon Project
 * 	    Information Technologies/Network Resources
 * 	    200 CCC
 * 	    Cornell University
 * 	    Ithaca, NY  14853-2601  USA
 * 
 * 	GateD is based on Kirton's EGP, UC Berkeley's routing
 * 	daemon	 (routed), and DCN's HELLO routing Protocol.
 * 	Development of GateD has been supported in part by the
 * 	National Science Foundation.
 * 
 * 	Please forward bug fixes, enhancements and questions to the
 * 	gated mailing list: gated-people@gated.cornell.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 *       Portions of this software may fall under the following
 *       copyrights:
 * 
 * 	Copyright (c) 1988 Regents of the University of California.
 * 	All rights reserved.
 * 
 * 	Redistribution and use in source and binary forms are
 * 	permitted provided that the above copyright notice and
 * 	this paragraph are duplicated in all such forms and that
 * 	any documentation, advertising materials, and other
 * 	materials related to such distribution and use
 * 	acknowledge that the software was developed by the
 * 	University of California, Berkeley.  The name of the
 * 	University may not be used to endorse or promote
 * 	products derived from this software without specific
 * 	prior written permission.  THIS SOFTWARE IS PROVIDED
 * 	``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * 	INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * 	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
