[mod.sources] Pathalias - new release

sources-request@genrad.UUCP (08/08/85)

Mod.sources:  Volume 2, Issue 36
Submitted by: Peter Honeyman (princeton!honey)

------------------- cut here --------------------------
: To unbundle, sh this file
echo README 1>&2
sed 's/^-//' >README <<'//GO.SYSIN DD README'
-From princeton!honey Wed Aug  7 00:02:19 EDT 1985
To: whom it may concern
Subject: new pathalias instructions

edit config.h
make
	peter
//GO.SYSIN DD README
echo CHANGES 1>&2
sed 's/^-//' >CHANGES <<'//GO.SYSIN DD CHANGES'
--- posted 8/85
Private hosts documented.
Homegrown scanner -- it's true what they say about lex.
Slight improvement in memory allocation.  More to come.
Build dbm file with pathalias ... | makedb ...
Don't print paths for private hosts, or anyone they collide with.
Domain-style addresses.  See man page.
Make links bidirectional by adding DEAD back link.  Dumb, expensive, and wrong.
Gatewayed nets.  See man page.

--- posted 1/85
By popular request, no ! in dbm key.
Network character must be one of !@%: -- dot is dead.
Private hosts.  (Undocumented.  Goal is to support host name collisions.)
Discourage mixing of left- (@) and right-associative (!) operators.  
Magic @ <-> % rule in paths involving multiple @'s.

--- from smb version
Directed graph.
Reverse the sense of the -c (cost) flag.
Don't complain about duplicate links -- use cheapest.
No network names in the output.

--- epoch
//GO.SYSIN DD CHANGES
echo pathalias.1 1>&2
sed 's/^-//' >pathalias.1 <<'//GO.SYSIN DD pathalias.1'
.TH PATHALIAS 1 
.SH NAME
pathalias, makedb \- electronic address router
.SH SYNOPSIS
.B pathalias
[
.B \-vci
] [
.B \-l
host
] [
.B \-d
link
] [
.ig
.\" the -g option is for pathparse.  it's not really used by pathalias.
.B \-g
file
] [
..
inputfiles
]
.PP
.B makedb
[
.B \-a
] [
.B \-o
dbmfile
] [
files ...
]
.SH DESCRIPTION
.I Pathalias
computes the shortest paths and corresponding routes from one
host to all other known, reachable hosts.
\fIPathalias\fP expects as input host-to-host connectivity
information, with
a host name in column 1, followed by white space, followed by
a comma-separated list of links (also host names),
denoting a connection from the host to the links.
Connections are assumed to be unidirectional.
A link-name may be preceded or followed by a network character to use
in the path name.
Valid network characters are `!', `@', `:', and `%'.
The link-name (and network character, if present) may be
followed by a ``cost'' in parentheses.
.PP
For example,
.RS
.nf
down	princeton!(DEDICATED)
princeton	topaz!(DEMAND+LOW)
topaz	@rutgers(LOCAL)
.fi
.RE
Costs may be arbitrary
arithmetic expressions involving numbers, parentheses, '+', '\-', '*',
and '/'.  Several symbolic numbers are
defined, as follows:
.PP
.RS
.nf
.ta 10m 20m
LOCAL	25	(local-area network connection)
DEDICATED	95	(high speed dedicated link)
DIRECT	200	(toll-free call)
DEMAND	300	(long-distance call)
HOURLY	500	(hourly poll)
EVENING	1800	(time restricted call)
DAILY	5000	(daily poll)
WEEKLY	30000	(irregular poll)
.fi
.RE
.PP
In addition,
DEAD is a very large number (effectively infinite),
and HIGH and LOW are \-5 and +5 respectively,
for baud-rate or quality bonuses/penalties.
.PP
The numbers are intended to represent frequency
of connection, which seems to be far more important than baud
rates for this type of traffic.  There is an assumed
high overhead for each hop; thus, e.g., HOURLY is far more than
DAILY / 24.
.PP
For the most part, arithmetic expressions that mix symbolic constants
other than HIGH and LOW make no sense.  Thus, \fIe.g.\fP, if a host
calls a local neighbor whenever there is work,
and additionally polls every evening, the cost is
DIRECT, \fInot\fP DIRECT+EVENING.
.PP
Aliases may be indicated by including lines of the form
.RS
name = alias [ , alias...]
.RE
The primary name is used in the output.
.PP
Fully connected networks, such as the ARPANET or a LAN,
are indicated by
.RS
net = {host, host, ...}
.RE
The host-list may be preceded or followed by a routing
character, and may be followed by a cost:
.RS
.nf
princeton-ethernet = {down, yoyo, flakey, quirky, princeton, ivy}!(LOCAL)
ARPA = @{sri-unix, mit-ai, su-score}(DEDICATED)
.fi
.RE
Also see the section on \fIgateways and domains\fP below.
.PP
Connection data may be given while hiding host names
by declaring
.RS
private {host [, host ...]}
.RE
.PP
.I Pathalias
will not generate routes for private hosts or for any otherwise declared
host with the same name, but may produce routes
through them.
The scope of a private declaration extends from the declaration to the end of
the input file in which it appears.
It is best to put private definitions at the beginning of the
appropriate input file.
.PP
Anything following # on an input line is ignored.  A line that begins with white
space is taken as a continuation of the previous line.
.PP
The output, which appears on the standard output,
is a list of host\-route pairs,
where route is a string appropriate for use with
\fIprintf\fP(3), e.g.
.RS
.nf
.ta 10m 20m
rutgers	princeton!topaz!%s@rutgers
.fi
.RE
The ``%s'' in the route string should be replaced by the
user name at the destination host.
(This task is normally performed by a mailer.)
.PP
Except in the case of \fidomains\fP (see below),
the name of a network is never used in
expansions; thus, in the above example, the path from sri-unix to
mit-ai would be '%s@mit-ai', not '%s@ARPA@mit-ai'.
.PP
Options:
.TP 6
.B  \-i
Map all host names to lower case.
.TP 6
.B  \-c
Print the costs of paths.
.TP 6
.B  \-v
Report some statistics on stderr.
.ig
.\" the -g option is for pathparse and is not for public consumption (yet!).
.TP 6
.BR \-g " file\^"
Dump the edges of the graph into the named file.
..
.TP 6
.BR  \-l " host\^"
Use host as local host name.
.TP 6
.BR  \-d " link\^"
Declares a dead link, host, or network.
If link is of the form host1!host2, the link from host1 to host2
is treated as an extremely high cost (i.e., dead) link.
If link is a single host name, that
host is treated as dead and will be used as an intermediate host of
last resort on any path.
If link is a network name, the network requires a gateway.
.PP
\fBGateways and domains.\fP\ \ A network is represented by
a pseudo-host and a set of network members.
Links from the network to the members have the weight given in
the input, while
links from the members to the network
have zero cost.
Networks that are declared dead on the command line show these
latter weights as expensive ones,
effectively prohibiting paths to members by way of the network.
If the input also shows non-zero weight links from some members to the network,
these hosts will act as gateways for the network.
E.g., if csnet is declared dead on the command line (with
the -d flag) and the input contains
.RS
.nf
csnet = {...}
csnet-relay	csnet
.fi
.RE
then routes to csnet hosts will use csnet-relay as a gateway,
rather than some other csnet host, one that might not
be able or willing to act as a gateway.
Furthermore, it is presumed that forwarding through
gatewayed networks is to be discouraged.
.PP
Some intermediate nodes expect routes to specify domain names, e.g.,
to get to bitnet host technion through the gateway at psuvax1,
the appropriate route might be psuvax1!technion.bitnet!user.
This syntax is generated if the routing character is repeated in
the declaration of the gateway to the ``domain'', e.g.,
.RS
.nf
bitnet = {..., technion, ...}!(DIRECT)
psuvax1	bitnet!!(DIRECT)
.fi
.RE
This syntax works with other routing characters as well, e.g.,
.RS
.nf
csnet = @{...}
csnet-relay @@csnet
.fi
.RE
.PP
.I Makedb
builds a
.IR dbm (3)
database from the named input files, or from the standard input if
no files are specified.
(This replaces the obsolete 
.I -b
flag of
.IR pathalias .)
Normally, the database is truncated;
if
.B \-a
is specified, the input records are appended to the existing database.
The
.B \-o
flag specifies the base name of the dbm file.
Input is expected to be sequence of ascii records
each consisting of a key field and a data field, separated by a single tab.
If the tab is missing, the data field is assumed to be empty.
.SH FILES
.ta \w'/usr/local/lib/palias.{dir,pag}     'u
/usr/local/lib/palias.{dir,pag}	default dbm output
.SH BUGS
White space in options is mandatory;
.I pathalias
should use
.IR getopt (3).
.br
The format string intended for
.I printf
is unable to anticipate the variety of addressing
rules.
In particular, if it contains an ``@'' and is given to
.I printf
along with an argument that also contains an ``@'', havoc is loosed.
.br
Domains constitute a futile attempt to defeat anarchy.
.br
The -i flag makes rice.rice impossible.
.br
.IR Dbm (3)
wants a non-empty data field, forcing
.I makedb
to be imaginative.
.SH COMPILE-TIME
Edit config.h to accommodate \s-2UNIX\s0 variants.
.SH AUTHORS
Steve Bellovin (ulysses!smb)
.br
Peter Honeyman (princeton!honey)

//GO.SYSIN DD pathalias.1
echo Makefile 1>&2
sed 's/^-//' >Makefile <<'//GO.SYSIN DD Makefile'
# pathalias -- by steve bellovin, as told to peter honeyman

# if you can't or don't intend to use dbm files, don't bother with makedb
DBM = -ldbm
# or if you roll your own ...
# DBM = dbm.o

CC = cc
CFLAGS =
LDFLAGS =
YFLAGS = -d

OBJ = addlink.o addnode.o extern.o local.o main.o mapit.o mapaux.o mem.o parse.o yylex.o
HDRS = def.h config.h
SRC = addlink.c addnode.c extern.c local.c main.c mapit.c mapaux.c mem.c parse.y yylex.c
CSRC = addlink.c addnode.c extern.c local.c main.c mapit.c mapaux.c mem.c parse.c yylex.c

pathalias: $(OBJ)
	$(CC) $(LDFLAGS) $(OBJ) $(LIBE) -o pathalias

all: pathalias makedb

$(OBJ):	def.h

# if touch had a proper exit status, this would work...
def.h: config.h
	touch def.h || get -s sccs/s.def.h

parse.c: parse.y def.h
	$(YACC) $(YFLAGS) parse.y
	mv y.tab.c parse.c

yylex.o:	parse.c yylex.c

makedb: makedb.o
	$(CC) makedb.o $(DBM) -o makedb

makedb.o: config.h

clean:
	rm -f *.o pa y.tab.c y.tab.h parse.c core

tags: $(SRC) $(HDRS) makedb.c
	ctags -w $(HDRS) $(SRC)

bundle:
	@bundle README CHANGES pathalias.1 Makefile ${HDRS} ${SRC} makedb.c

lint:	$(CSRC)
	lint $(CFLAGS) $(CSRC)
	lint makedb.c


# the remainder is site specific and can be deleted.

# administering paths on 6 machines is easy -- here's how i do it

# hosts running delivermail
DMEQUIV = tilt

# hosts running sendmail
SMEQUIV = quirky flakey yoyo

# all neighbors
NEIGHBORS = ${DMEQUIV} ${SMEQUIV} princeton

# including me
SITES = down ${NEIGHBORS}

# in v8, * includes . files, sh has extended syntax.
PATHFILES = paths/[^.]* paths.bell/[^.]* path.map/[^.]*

# from observation and rumor
# avoid like the plague
DEADHOSTS = -d harpo -d zeppo -d chico -d gummo -d tucc -d hogpc -d eosp1 -d twg

DEADLINKS = -d fortune!analog -d uiucdcs!uokmet -d siemens!uiucdcs -d vax135!uw-beaver -d research!eagle -d decvax!humus -d ulysses!ucbarpa

# nets that require gateways
DEADNETS = -d CSNET -d BITNET -d ARPA -d DEC -d MAILNET

DEAD = ${DEADHOSTS} ${DEADLINKS} ${DEADNETS}

# map output to lower case.  dead links.
ARGS = -i $(DEAD)

paths:	${SITES}

# don't touch "down" until completely done.
down:	paths/princeton
	pathalias -v ${ARGS} $(PATHFILES) > down.new 2>ERRORS
	sort down.new > down
#	rm down.new

# NEIGHBORS have exactly the same links as down, so turn
#	down	%s
#	up	up!%s
# into
#	up	%s
#	down	down!%s

# generate phoney costs for delivermail neighbors
${DMEQUIV}:	down
	sed -e "s/^down	%s$$/$@	%s/" -e "s/^$@	$@!%s$$/down	down!%s/" -e 's/^/0	/' down > $@
	
# reorder the output and generate phoney costs for sendmail neighbors
${SMEQUIV}: down
	sed -e "s/^down	%s$$/$@	%s/" -e "s/$@	$@!%s$$/down	down!%s/" -e 's/\(.*\)	\(.*\)/\1	0	\2/' down > $@

# ship it!
sendit: ${NEIGHBORS}
	for i in ${DMEQUIV}; do\
		uucp -ga $$i $$i!/usr/local/lib/pathaliases;\
		uux -z -gb $$i!newaliases;\
	done
	for i in ${SMEQUIV} princeton; do uucp $$i $$i!~/paths; done
	touch sendit

# reorder the output for princeton/sendmail/uubang and generate phoney costs.
princeton: down
	pathalias ${ARGS} -l $@ $(PATHFILES)> $@.new 2>ERRORS
	sed -e 's/\(.*\)	\(.*\)/\1	0	\2/' $@.new > $@
	rm $@.new

# make output for friends.  (make friends for output?)
sfyog rcalabs neoucom:
	pathalias ${ARGS} -l $@ $(PATHFILES) > $@ 2>ERRORS

# desperation debugging -- examine the costs.
costs:
	pathalias -vci ${DEAD} -l down $(PATHFILES) > down.costs 2>ERRORS

# make one BIG file.  a bad idea.
cat:
	cat $(PATHFILES) > CAT

# generate test data using undocumented features.  (go away.)
edges: down
	pathalias -i -g edges $(PATHFILES)

//GO.SYSIN DD Makefile
echo def.h 1>&2
sed 's/^-//' >def.h <<'//GO.SYSIN DD def.h'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
#ifdef MAIN
static char	*h_sccsid = "@(#)def.h	7.1 (down!honey) 85/08/06";
#endif MAIN
#endif lint

#include <stdio.h>
#include <ctype.h>
#include "config.h"

typedef	long Cost;
typedef struct node node;
typedef struct link link;

#ifdef lint
#define vprintf fprintf
#else !lint -- this gives null effect warning
/* because it's there ... */
#define vprintf		!Vflag ? 0 : fprintf
#endif lint

/* scanner (yylex) states */
#define OTHER 0
#define COSTING 1
#define NEWLINE 2
#define PRIVATING 3

#define	isnetc(c)	((c)=='!' || (c)==':' || (c)=='@' || (c)=='%')

#define dirbits(c)	(c)

/* flags for n_flag */
#define ISPRIVATE  0x0001 /* this node invisible outside its definition file */
#define DEHASH	   0x0002 /* removed from hash table yet? */
#define ATSIGN	   0x0004 /* seen an at sign?  used for magic @/% rules */
#define MAPPED	   0x0008 /* done mapping this node */
#define	NDEAD	   0x0010 /* node is dead */
#define HASLEFT	   0x0020 /* route has a left side net character */
#define HASRIGHT   0x0040 /* route has a right side net character */
#define	NNET	   0x0080 /* node is a network name */
#define INDFS	   0x0100 /* used when removing net cycles */
#define DUMP	   0x0200 /* we have dumped this net's edges */
#define NDOMAIN	   0x0400 /* use .domain style addressing */
#define GATEWAYIN  0x0800 /* heaped via gatewayed net */
#define COLLISION  0x1000 /* collides with a private host name */

#define DEADNET(n) (((n)->n_flag & (NNET | NDEAD)) == (NNET | NDEAD))

/*
 * save some space in nodes -- there are > 10,000 allocated!
 *
 *	node	*n_net		others in this network (parsing)
 * 	node	*n_root		root of net cycle (mapping)
 *
 *	node	*n_private	other privates in this file (parsing)
 *	char	*n_path		path to this host (mapping)
 *		
 */

#define n_root n_unetroot.nu_root
#define n_net n_unetroot.nu_net

#define n_private n_uprivatepath.nu_private
#define n_path n_uprivatepath.nu_path

struct node {
	char	*n_name;	/* host name */
	link	*n_link;	/* head of adjacency list */
	node	*n_alias;	/* real node (when this node is an alias) */
	node	*n_aliaslink;	/* other aliases for this node */
	union {
	    node	*nu_root;	/* root of net cycle (mapping) */
	    node	*nu_net;	/* others in this network (parsing) */
	}	n_unetroot;
	union {
	    node	*nu_private;	/* other privates in this file (parsing) */
	    char	*nu_path;	/* path to this host (mapping) */
	}	n_uprivatepath;	
	Cost	n_cost;		/* cost to this host */
	short	n_tloc;		/* back ptr to heap/hash table */
	short	n_flag;		/* see manifests above */
};

#define	DEFNET	'!'			/* default network character */
#define	DEFDIR	LLEFT			/* host on left in default net */
#define	DEFCOST	((Cost) 4000)		/* default cost of a link */
#define	INF	((Cost) 30000000)	/* infinitely expensive link */

/* data structure for adjacency list representation */

/* flags for l_dir */

/*
 * there's an ugly dependency between the following manifests and the
 * variable Netchars = "!:^@%", defined in extern.c.  this saves 2
 * bytes per link (of which there are well over 20k).  this does not
 * mean i'm satsified with bad design.
 */
#define NETDIR(l)	((l)->l_flag & LDIR)
#define NETCHAR(l)	(Netchars[(l)->l_flag & LNETCHARS])

#define LNETCHARS	0x3
#define LBANG		0x0
#define LCOLON		0x1
#define LAT		0x2
#define LPERCENT	0x3

#define LDIR	0x8	/* 0 for left, 1 for right */
#define LRIGHT	0x0	/* user@host style */
#define LLEFT	0x8	/* host!user style */

#define LDEAD	0x10	/* this link is dead */
#define LDOMAIN 0x20	/* use host.domain.  i feel sick. */

struct link {
	link	*l_next;	/* rest of adjacency list */
	node	*l_to;		/* adjacent node */
	Cost	l_cost;		/* edge cost */
	char	l_flag;		/* right/left syntax */
};

node	*addnode(), *newnode(), **newtable(), *addprivate();
link	*addlink(), *lmerge(), *newlink();
char	*strsave(), *local();
void	setpath(), pack();

#define STATIC static

extern node	*Home;
extern char	*Cfile;
extern int	Fcnt;
extern char	**Ifiles;
extern char	*ProgName;
extern int	Lineno;
extern node	**Table;
extern int	Tabsize;
extern char	*Netchars;
extern int	Vflag;
extern int	Cflag;
extern int	Iflag;
extern int	Ncount;
extern int	Lcount;
extern char	*Pathout;
extern char	*Graphout;
extern char	*Linkout;
extern node	*Private;
extern int	Hashpart;
extern int	Scanstate;

//GO.SYSIN DD def.h
echo config.h 1>&2
sed 's/^-//' >config.h <<'//GO.SYSIN DD config.h'
/* pathalias -- by steve bellovin, as told to peter honeyman */

/* use strchr (strrchr), not index (rindex) -- probably system v */
/* #define STRCHR /* */

/* uname() -- probably system v or 8th ed. */
#define UNAME /* */

/* memset() -- probably system v or 8th ed. */
#define MEMSET /* */

/* gethostname() -- probably 4.2bsd */
/* #define GETHOSTNAME	/* */

/* bzero() -- probably 4.2bsd */
/* #define BZERO /* */

/* default place for dbm output of makedb; can use -o file at run-time  */
#define	ALIASDB	"/usr/local/lib/palias"

/*
 * after much profiling, i finally found a decent malloc/free
 * remove the next line if you disagree
 */
#define MYMALLOC	/**/

/*
 * how many trailing 0's needed in a pointer?
 *
 * vax doesn't care, but setting ALIGN to 2 saves about 5% in time, at
 * the expense of about 2% in space.  why bother?
 *
 * i am told that the 68000 and 3b20 want ALIGN to be 1.
 *
 * perkin-elmer 3220 wants ALIGN to be 2. 
 */
#define ALIGN 0

/****************************************/
/*	END OF CONFIGURATION SECTION	*/
/*		EDIT NO MORE		*/
/****************************************/
#ifdef MAIN
#ifndef lint
static char	*c_sccsid = "@(#)config.h	7.1 (down!honey) 85/08/06";
#endif lint
#endif MAIN

#ifdef MYMALLOC

#define malloc mymalloc
#define free myfree
char	*sbrk(), *mymalloc();

#ifdef ALIGN

#if ALIGN == 0
#undef ALIGN
#endif ALIGN == 0

#endif ALIGN

#ifndef ALIGN
#define memget sbrk
#else ALIGN
char	*memget();
#endif ALIGN

#endif MYMALLOC

#ifdef STRCHR
#define index strchr
#define rindex strrchr
#endif STRCHR

#ifdef BZERO
#define strclear(s, n)	((void) bzero((s), (n)))
#else !BZERO
#	ifdef MEMSET
char	*memset();
#	define strclear(s, n)	((void) memset((s), 0, (n)))
#	else !MEMSET
	(void) strclear();
#	endif MEMSET
#endif BZERO

long	atol();
char	*malloc();
char	*index();
char	*rindex();
FILE	*popen();
char	*strcpy();
//GO.SYSIN DD config.h
echo addlink.c 1>&2
sed 's/^-//' >addlink.c <<'//GO.SYSIN DD addlink.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)addlink.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"
link	*
addlink(from, to, cost, netchar, netdir)
node	*from;
register node	*to;
Cost	cost;
char	netchar;
char	netdir;
{
	register link	*l, *prev = 0;

#ifndef SQUANDER
	/* welcome to cycle city -- inner loop follows */

	/*
	 * link chains are sorted by host struct address, largest to smallest.
	 * thus, newly declared hosts are at the front of the list.
	 */
	for (l = from->n_link; l; l = l->l_next) {
		if (to >= l->l_to)
			break;
		prev = l;
	}
	/* you are now leaving cycle city -- hope you enjoyed your stay */

	if (l && (to == l->l_to)) {
		/*
		 * this is twisted by the awful gateway semantics.
		 *
		 * if "to" is a dead network, use the cheapest non-zero cost.
		 * ("from" is a gateway.)
		 *
		 * otherwise, use new cost if cheaper.
		 */
		if ((DEADNET(to) && l->l_cost == 0) || cost < l->l_cost) {
			l->l_cost = cost;
			netbits(l, netchar, netdir);
		}
		return(l);
	}

#endif !SQUANDER
	l = newlink();

	if (prev) {
		l->l_next = prev->l_next;
		prev->l_next = l;
	} else {
		l->l_next = from->n_link;
		from->n_link = l;
	}

	l->l_to = to;
	l->l_cost = cost;
	if (netchar == 0) {
		netchar = DEFNET;
		netdir = DEFDIR;
	}
	netbits(l, netchar, netdir);

	return(l);
}

deadlink(s) 
char	*s;
{
	char	*t, c;
	link	*l;

	for (t = s; !isnetc(*t); t++)
		if (*t == 0) 
			break;
	if ((c = *t) != 0) {
		*t = 0;
		l = addlink(addnode(s), addnode(t + 1), INF / 2, c, DEFDIR);
		l->l_flag |= LDEAD;
	} else 
		addnode(s)->n_flag |= NDEAD;
}

netbits(l, netchar, netdir)
link	*l;
char	netchar, netdir;
{
	int	isadomain = 0;
	char	*nptr;

	if (netchar & 0200) {
		netchar &= 0177;
		isadomain = LDOMAIN;
	}
	if ((nptr = index(Netchars, netchar)) == 0) {
		fprintf(stderr, "%s: unknown network operator: %c\n",
								ProgName, netchar);
		badmagic(1);
	}
	l->l_flag &= ~(LNETCHARS|LDIR|LDOMAIN);
	l->l_flag |= (nptr - Netchars) | dirbits(netdir) | isadomain;
}
//GO.SYSIN DD addlink.c
echo addnode.c 1>&2
sed 's/^-//' >addnode.c <<'//GO.SYSIN DD addnode.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)addnode.c	7.1 (down!honey) 85/08/07";
#endif lint

#include "def.h"

void	lowercase();
node	*isprivate();

/*
 * these numbers are chosen because:
 *	-> they are prime,
 *	-> they are monotonic increasing,
 *	-> each is a tad smaller than a multiple of 1024.
 * the first point yields good hash functions, the second is used for the
 * standard re-hashing implementation of open addressing, and the third
 * optimizes for quirks in some mallocs i have seen.
 */
STATIC int Primes[]	= {
	1021, 2039, 3067, 4093, 5113, 6133, 7159, 8179, 9209,
	10223, 11261, 12281, 13309, 14327, 15349, 16381, 17401,
	18427, 19447, 20477, 21499, 22511, 23549, 24571, 25589, 0
};
STATIC int	Tabindex = -1;
STATIC int	Collision;	/* mark host name collisions in hash() */

int	Tabsize;	/* used later for the priority queue */
node	**Table;	/* ditto */

node	*
addnode(name)
register char	*name;
{
	register int	i;
	register node	*n;

	if (Iflag)
		lowercase(name);

	/* is it a private host? */
	n = isprivate(name);
	if (n != 0) {
		while (n->n_alias)
			n = n->n_alias;
		return(n);
	}

	i = hash(name, 0);
	if (Table[i] != 0) {
		n = Table[i];
		while (n->n_alias)
			n = n->n_alias;
		return(n);
	}

	n = newnode();
	n->n_name = strsave(name);
	Table[i] = n;
	n->n_tloc = i;	/* essentially a back link to the table */
	if (Collision)
		n->n_flag |= COLLISION;	/* name collision with private host */

	return(n);
}

alias(parent, child) 
register node	*parent;	/* real node */
register node	*child;		/* alias node */
{
	register node	*root;		/* root of this alias tree */

	if (parent == child) {
		char	buf[BUFSIZ];

		sprintf(buf, "warning: alias redeclaration: %s = %s",
			parent->n_name, parent->n_name);
		yyerror(buf);
		return;		/* caused by redeclaration of alias */
	}

	/* avoid alias loops, force many-to-one */
	/* can't happen -- wish it could ... */
	if (parent->n_alias || child->n_alias) {
		yyerror("can't nest aliases");
		return;
	}

	/* merge links from parent(s) to root, point parent at root */
	for (root = parent->n_alias; root; root = root->n_alias) {
		root->n_link = lmerge(root->n_link, parent->n_link);
		parent->n_link = 0;
		parent = root;
	}

	/* merge child links into parent (now root) */
	parent->n_link = lmerge(parent->n_link, child->n_link);
	child->n_link = 0;

	/* set up the alias pointers */
	child->n_alias = parent;
	child->n_aliaslink = parent->n_aliaslink;
	parent->n_aliaslink = child;
}

/* double hashing */
#define HASH1(n)	((n) % Tabsize);
#define HASH2(n)	(((n) % (Tabsize - 2)) + 1)

/*
 * at 75% full, probes per access is about 2.
 */
#define HIGHWATER	75
#define LOWWATER	50
#define isfull(n, size)	((n) > (((size) * HIGHWATER) / 100))
#define isempty(n, size)	((n) < (((size) * LOWWATER) / 100))

STATIC int
hash(name, unique)
char	*name;
{
	register int	probe, hash2;
	register node	*n;

	if (Tabindex < 0) {			/* first time */
		Tabindex = 0;
		Tabsize = Primes[0];
		Table = newtable(Tabsize);
	}

	if (isfull(Ncount, Tabsize))
		rehash();			/* more, more! */
				
	probe = fold(name);
	/* don't change the order of the next two lines */
	hash2 = HASH2(probe);
	probe = HASH1(probe);
	/* thank you! */

	/*
	 * probe the hash table.
	 * if unique is set, we require a fresh slot.
	 * otherwise, use double hashing to find either
	 *  (1) an empty slot, or
	 *  (2) a non-private copy of this host name
	 *
	 * as a side effect, if we notice a collision with a private host
	 * we mark the other so that it is skipped at output time.
	 */
	Collision = 0;
	while ((n = Table[probe]) != 0) {
		if (strcmp(n->n_name, name) == 0) {
			if (unique)
				n->n_flag |= COLLISION;
			else if (n->n_flag & ISPRIVATE)
				Collision++;
			else
				break;	/* this is it! */
		}
		probe -= hash2;
		if (probe < 0)
			probe += Tabsize;
	}
	return(probe);
}

STATIC int
rehash()
{
	register node	**otable, **optr;
	register int	probe;
	int	osize;

#ifdef DEBUG
	hashanalyze();
#endif DEBUG
	optr = Table + Tabsize - 1;	/* ptr to last */
	otable = Table;
	osize = Tabsize;

	do {
		Tabsize = Primes[++Tabindex];
		if (Tabsize == 0) {
			fprintf(stderr, "%s: > %d hosts\n", ProgName,
							Primes[Tabindex-1]);
			badmagic(1);
		}
	} while (!isempty(Ncount, Tabsize));
	vprintf(stderr, "rehash into %d\n", Tabsize);
	Table = newtable(Tabsize);

	do {
		if (*optr == 0)
			continue;
		probe = hash((*optr)->n_name, (*optr)->n_flag & ISPRIVATE);
		if (Table[probe] != 0) {
			fprintf(stderr, "%s: rehash error\n", ProgName);
			badmagic(1);
		}
		Table[probe] = *optr;
		(*optr)->n_tloc = probe;
	} while (optr-- > otable);
	freetable(otable, osize);
}


STATIC
fold(s)
register char	*s;
{
	register int	sum = 0;

	while (*s) {
		sum <<= 2;
		sum += *s++;
	}
	if (sum < 0) 
		sum = -sum;
	return(sum);
}

/* merge the l2 list into the l1 list */
link	*
lmerge(l1, l2)
register link	*l1, *l2;
{
	register link	*ltmp;
	link	*rval;

	if (l1 == 0)
		return(l2);

	if (l2 == 0)
		return(l1);

	if (l1->l_to > l2->l_to) {
		ltmp = rval = l1;
		l1 = l1->l_next;
	} else if (l1->l_to < l2->l_to) {
		ltmp = rval = l2;
		l2 = l2->l_next;
	} else if (l1->l_cost <= l2->l_cost) {
		ltmp = rval = l1;
		l1 = l1->l_next;
		free((char *) l2);
		l2 = l2->l_next;
	} else {
		ltmp = rval = l2;
		l2 = l2->l_next;
		free((char *) l1);
		l1 = l1->l_next;
	}

	while (l1 && l2) {
		if (l1->l_to > l2->l_to) {
			ltmp->l_next = l1;
			ltmp = l1;
			l1 = l1->l_next;
		} else if (l1->l_to < l2->l_to) {
			ltmp->l_next = l2;
			ltmp = l2;
			l2 = l2->l_next;
		} else if (l1->l_cost <= l2->l_cost) {	/* use cheaper link */
			ltmp->l_next = l1;
			ltmp = l1;
			l1 = l1->l_next;
			free((char *) l2);
			l2 = l2->l_next;
		} else {
			ltmp->l_next = l2;
			ltmp = l2;
			l2 = l2->l_next;
			free((char *) l1);
			l1 = l1->l_next;
		}
	}
	if (l1)
		ltmp->l_next = l1;
	else
		ltmp->l_next = l2;

	return(rval);
}

hashanalyze()
{
	int	probe, hash2, count, i, collision[5];
	int	longest = 0, total = 0, slots = 0;
	int	nslots = sizeof(collision)/sizeof(collision[0]);

	if (!Vflag)
		return;

	strclear((char *) collision, sizeof(collision));
	for (i = 0; i < Tabsize; i++) {
		if (Table[i] == 0)
			continue;
		/* private hosts too hard to account for ... */
		if (Table[i]->n_flag & ISPRIVATE)
			continue;
		count = 1;
		probe = fold(Table[i]->n_name);
		/* don't change the order of the next two lines */
		hash2 = HASH2(probe);
		probe = HASH1(probe);
		/* thank you! */
		while (Table[probe] != 0
		    && strcmp(Table[probe]->n_name, Table[i]->n_name) != 0) {
			count++;
			probe -= hash2;
			if (probe < 0)
				probe += Tabsize;
		}
		if (Table[probe] == 0) {
			fprintf(stderr, "%s: impossible hash error for %s\n",
					ProgName, Table[i]->n_name);
			continue;
		}
		
		total += count;
		slots++;
		if (count > longest)
			longest = count;
		if (count >= nslots)
			count = 0;
		collision[count]++;
	}
	for (i = 1; i < nslots; i++)
		if (collision[i])
			fprintf(stderr, "%d chains: %d (%d%%)\n",
				i, collision[i], (collision[i] * 100)/ slots);
		if (collision[0])
			fprintf(stderr, "> %d chains: %d (%d%%)\n",
				nslots - 1, collision[0],
				(collision[0] * 100)/ slots);
	fprintf(stderr, "%2.2f probes per access, longest chain: %d\n",
		(double) total / slots, longest);
}

STATIC void
lowercase(s)
register char	*s;
{
	do {
		*s = isupper(*s) ? tolower(*s) : *s;
	} while (*s++);
}

STATIC node	*
isprivate(name)
register char	*name;
{
	register node	*n;

	for (n = Private; n != 0; n = n->n_private)
		if (strcmp(name, n->n_name) == 0)
			return(n);
	return(0);
}

fixprivate()
{
	register node	*n, *next;
	register int	i;

	for (n = Private; n != 0; n = next) {
		n->n_flag |= ISPRIVATE;		/* overkill, but safe */
		i = hash(n->n_name, 1);
		if (Table[i] != 0) {
			fprintf(stderr, "%s: impossible private node error on %s\n",
				ProgName, n->n_name);
			badmagic(1);
		}
	
		Table[i] = n;
		n->n_tloc = i;	/* essentially a back link to the table */
		next = n->n_private;
		n->n_private = 0;	/* clear for later use */
	}
	Private = 0;
}

node	*
addprivate(name)
register char	*name;
{
	register node	*n;

	if (Iflag)
		lowercase(name);
	n = isprivate(name);
	if (n)
		return(n);	/* this isn't even worth a warning */

	n = newnode();
	n->n_name = strsave(name);
	n->n_private = Private;
	Private = n;
	return(n);
}
//GO.SYSIN DD addnode.c
echo extern.c 1>&2
sed 's/^-//' >extern.c <<'//GO.SYSIN DD extern.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)extern.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

node	*Home;
int	Fcnt = -1;
char	*Cfile;
char	**Ifiles;
char	*ProgName;
int	Vflag;
int	Cflag;
int	Iflag;
int	Lineno = 1;
char	*Netchars = "!:@%";	/* sparse, but sufficient */
int	Ncount;
int	Lcount;
char	*Graphout;
char	*Linkout;
node	*Private;		/* list of private nodes in this file */
int	Hashpart;		/* used while mapping -- above is heap */
int	Scanstate = NEWLINE;	/* scanner (yylex) state */
//GO.SYSIN DD extern.c
echo local.c 1>&2
sed 's/^-//' >local.c <<'//GO.SYSIN DD local.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)local.c	7.1 (down!honey) 85/08/06";
#endif lint

#include <stdio.h>
#include "config.h"

#ifdef	UNAME
#include <sys/utsname.h>

char	*
local()
{
	static struct utsname utsname;

	uname(&utsname);
	return(utsname.nodename);
}

#else !UNAME

char	*
local()
{
	static char lname[64];
	void	gethostname();

	gethostname(lname, sizeof(lname));
	return(lname);
}

#ifndef GETHOSTNAME

STATIC void
gethostname(name, len)
char	*name;
{
	FILE	*whoami, *fopen(), *popen();
	char	*ptr, *index();

	*name = '\0';

	/* try /etc/whoami */
	if ((whoami = fopen("/etc/whoami", "r")) != 0) {
		(void) fgets(name, len, whoami);
		(void) fclose(whoami);
		if ((ptr = index(name, '\n')) != 0)
			*ptr = '\0';
	}
	if (*name)
		return;

	/* try /usr/include/whoami.h */
	if ((whoami = fopen("/usr/include/whoami.h", "r")) != 0) {
		while (!feof(whoami)) {
			char	buf[100];

			if (fgets(buf, 100, whoami) == 0)
				break;
			if (sscanf(buf, "#define sysname \"%[^\"]\"", name))
				break;
		}
		(void) fclose(whoami);
		if (*name)
			return;
	}

	/* ask uucp */
	if ((whoami = popen("uuname -l", "r")) != 0) {
		(void) fgets(name, len, whoami);
		(void) pclose(whoami);
		if ((ptr = index(name, '\n')) != 0)
			*ptr = '\0';
	}
	if (*name)
		return;
	
	/* aw hell, i give up!  is this a real unix? */
	return;
}
#endif GETHOSTNAME
#endif UNAME
//GO.SYSIN DD local.c
echo main.c 1>&2
sed 's/^-//' >main.c <<'//GO.SYSIN DD main.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
#define MAIN
static char	*sccsid = "@(#)main.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

main(argc, argv) 
int	argc; 
char	*argv[];
{
	char	*locname = 0;

#ifdef lint
	argc = argc;
#endif lint

	ProgName = argv[0];
	while (*++argv && **argv == '-') {
		(*argv)++;
		switch(**argv) {

		case 'l':	/* local name */
			locname = *++argv;
			if (!*locname)  {
				fprintf(stderr, "%s: -l requires host name\n",
					ProgName);
				exit(1);
			}
			break;

		case 'd':	/* dead host or link */
			if (!*++argv) {
				fprintf(stderr, "%s: -d requires host name\n",
					ProgName);
				exit(1);
			}
			deadlink(*argv);
			break;

		case 'g':	/* graph output file */
			Graphout = *++argv;
			if (!*Graphout)  {
				fprintf(stderr, "%s: -g requires output file name\n",
					ProgName);
				exit(1);
			}
			break;

		case 's':	/* show cheapest links */
			Linkout = *++argv;
			if (!*Linkout)  {
				fprintf(stderr, "%s: -s requires output file name\n",
					ProgName);
				exit(1);
			}
			break;

		case 0:
			break;	/* ignore naked '-' */

		default:
			while (**argv) {
				switch (**argv) {
			
				case 'v':	/* verbose stderr, somewhat boring */
					Vflag++;
					break;

				case 'c':	/* print cost info */
					Cflag++;
					break;

				case 'i':	/* ignore case */
					Iflag++;
					break;

				default:
					fprintf(stderr, "%s: -%c: unknown flag\n", ProgName,
						**argv);
					exit(1);
				}
				(*argv)++;
			}
		}
	}

	Fcnt++;
	if (*argv) {
		Ifiles = argv;
		freopen("/dev/null", "r", stdin);
	}

	if (!locname) 
		locname = local();
	if (*locname == 0) {
		locname = "lostinspace";
		fprintf(stderr, "%s: using \"%s\" for local name\n",
				ProgName, locname);
	}

	Home = addnode(locname);	/* add home node */
	Home->n_cost = 0;		/* doesn't cost to get here */

	yyparse();			/* read in link info */

	hashanalyze();

	while (Home->n_alias) 
		Home = Home->n_alias;	/* bad practice, but conceivable */

	mapit();			/* compute shortest paths */

	exit(0);
}

badmagic(n)
{
#ifdef DEBUG
	abort();
#else !DEBUG
	exit(n);
#endif DEBUG
}
//GO.SYSIN DD main.c
echo mapit.c 1>&2
sed 's/^-//' >mapit.c <<'//GO.SYSIN DD mapit.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)mapit.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

void	reheap(), insert(), setpath(), swap();
node	*min_node();

STATIC int	Nheap;

mapit()
{
	node *n, *next;
	link *l;
	Cost	cost, setcost();
	char	*sbrk();

	vprintf(stderr, "%d vertices, %d edges\n", Ncount, Lcount);
	vprintf(stderr, "break is %ld after parsing\n", (long) sbrk(0));
	
	/* use the hash Table for the heap */
	/* TODO: coalesce the following functions into a single one */
	pack();		/* remove holes in the Table */
	amerge();	/* merge all alias links once and for all */
	if (Linkout && *Linkout)	/* dump cheapest links */
		showlinks();
	if (Graphout && *Graphout)	/* dump the edge list */
		dumpgraph();

	while (Home->n_alias)
		Home = Home->n_alias;
	dehash(Home);
	Home->n_path = strsave("%s");
	insert(Home);

	/*
	 * main mapping loop.
	 * assertion: no alias can ever appear in the heap.  'struth.
	 */
	while ((n = min_node()) != 0) {

		printit(n);

		/*
		 * if reached by a gatewayed net, discourage further links.
		 * this has some relevance to common carriers and the FCC ...
		 */
		if (n->n_flag & GATEWAYIN)
			n->n_cost += 2* DEFCOST;

		/* add children to heap */
		for (l = n->n_link; l != 0; l = l->l_next) {
			next = l->l_to;
			while (next->n_alias)
				next = next->n_alias;
			if (next->n_flag & MAPPED)
				continue;
			dehash(next);

			cost = setcost(n, l, next);

			if (next->n_cost == 0) {		/* first time */
				next->n_cost = cost;
				insert(next);
			} else if (cost < next->n_cost) {	/* cheaper route */
				next->n_cost = cost;
				reheap(next);
			} else					/* lose lose */
				continue;

			/* note whether we got here via a gatewayed net */
			if (DEADNET(n))
				next->n_flag |= GATEWAYIN;
			else
				next->n_flag &= ~GATEWAYIN;

			setpath(n, next, l);

			free((char *) l);	
		}

		/* done with this node forever -- free as much as possible */
		free((char *) n->n_path);	/* absolutely free ... */
		free((char *) n->n_name);

		/* expunge aliases */
		for (next = n->n_aliaslink; next; next = next->n_aliaslink) {
			dehash(next);
			Table[next->n_tloc] = 0;
			next->n_tloc = 0;
			free((char *) next->n_name);
		}
	}
	vprintf(stderr, "break is %ld at after mapping\n", (long) sbrk(0));

	if (Nheap != 0) {
		fprintf(stderr, "%s: null entry found in heap\n", ProgName);
		badmagic(1);
	}

	if (Hashpart < Tabsize) {
		fprintf(stderr, "You can't get there from here:\n");
		while (Hashpart < Tabsize) {
			n = Table[Hashpart++];
			if (n->n_alias)
				continue;	/* primary hosts only */
			fprintf(stderr, "\t%s", n->n_name);
			if (n->n_aliaslink) {
				fprintf(stderr, " (alias %s", n->n_aliaslink->n_name);
				n = n->n_aliaslink;
				while (n = n->n_aliaslink)
					fprintf(stderr,  ", %s", n->n_name);
				putc(')', stderr);
			}
			putc('\n', stderr);
		}
	}
}

STATIC Cost
setcost(n, l, next)
register node	*n;
register link	*l;
node	*next;
{
	register Cost	cost;

	cost = n->n_cost + l->l_cost;	/* fundamental cost */

	/*
	 * heuristics:
	 *    charge for getting out of a dead host.
	 *    charge for getting in to a dead net
	 *         unless the link cost is non-zero (gateway).
	 *    charge for a dead link.
	 *    discourage mixing of left and right syntax.
	 */
	if ((n->n_flag & (NNET | NDEAD)) == NDEAD)
		cost += INF/2;	/* dead host */
	if (DEADNET(next) && l->l_cost == 0)
		cost += INF/2;	/* dead net, not gateway */
	if (l->l_flag & LDEAD)
		cost += INF/2;	/* dead link */
	if ((n->n_flag & HASLEFT) && (NETDIR(l) == LRIGHT))
		cost += DEFCOST;	/* mix */
	if ((n->n_flag & HASRIGHT) && (NETDIR(l) == LLEFT))
		cost += DEFCOST;	/* mix */

	return(cost);
}

/*
 * heap implementation of priority queue.
 */

STATIC void
insert(n)
node	*n;
{
	int	i, parent;

	Table[n->n_tloc] = 0;
	if (Table[Nheap+1] != 0) {
		fprintf(stderr, "%s: heap error in insert\n", ProgName);
		badmagic(1);
	}
	if (Nheap++ == 0) {
		Table[1] = n;
		n->n_tloc = 1;
		return;
	}

	/* insert at the end and percolate up */
	Table[Nheap] = n;
	n->n_tloc = Nheap;
	for (i = Nheap; i > 1; i = parent) {
		if (Table[i] == 0) {
			fprintf(stderr, "%s: heap error in insert\n", ProgName);
			badmagic(1);
		}
		parent = i / 2;
		if (Table[i]->n_cost < Table[parent]->n_cost)
			swap(i, parent);
	}
	return;
}

STATIC node	*
min_node()
{
	node	*rval;
	int	i;

	if (Nheap == 0)
		return(0);

	rval = Table[1];	/* return this one */
			
	/* move last entry into root, percolate down */
	Table[1] = Table[Nheap];
	Table[1]->n_tloc = 1;
	Table[Nheap] = 0;
	if (--Nheap == 0)
		return(rval);

	i = 1;
	for (;;) {
		/* swap with smaller child  (if larger than same) */
		int	child;

		child = i * 2;
		if (child > Nheap)
			break;
		if (child < Nheap) 	/* right child exists? */
			if (Table[child]->n_cost > Table[child+1]->n_cost)
				child++;
			
		if (Table[i]->n_cost > Table[child]->n_cost)
			swap(i, child);
		i = child;
	}

	return(rval);
}

STATIC void
swap(i, j)
{
	node	*temp;

	temp = Table[i];
	Table[i] = Table[j];
	Table[j] = temp;
	Table[j]->n_tloc = j;
	Table[i]->n_tloc = i;
}

/* "percolate" node n up the heap by exchanging with the parent */
STATIC void
reheap(n)
node	*n;
{
	int	loc, parent;
	Cost	cost;

	cost = n->n_cost;
	for (loc = n->n_tloc; loc != 1; loc = parent) {
		parent = loc / 2;
		if (cost >= Table[parent]->n_cost)
			return;
		swap(loc, parent);
	}
}

STATIC void
setpath(prev, next, l) 
node	*prev, *next;
link	*l;
{
	char	pathbuf[BUFSIZ], hostbuf[BUFSIZ], *hptr;
	char	netchar, netdir;

	netchar = NETCHAR(l);
	netdir = NETDIR(l);
	/* undo settings from earlier calls */
	if (next->n_path)
		free((char *) next->n_path);	/* absolutely free ... */

	if (prev->n_flag & ATSIGN)
		next->n_flag |= ATSIGN;
	else
		next->n_flag &= ~ATSIGN;

	if (prev->n_flag & HASLEFT)
		next->n_flag |= HASLEFT;
	else
		next->n_flag &= ~HASLEFT;

	if (prev->n_flag & HASRIGHT)
		next->n_flag |= HASRIGHT;
	else
		next->n_flag &= ~HASRIGHT;

	if (next->n_flag & NNET) {
		/*
		 * grumble. when climbing onto a "domain" style network,
		 * append .netname.  but we can't do it 'til later ...
		 *
		 * unless, of course, we are in transit from another
		 * domain style network, in which case we tack the
		 * predecessor's name onto the next domain.
		 *
		 * e.g., prev = arpa, next = csnet.  change next->n_name
		 * to csnet.arpa.  but first wipe out any previous
		 * domain on next.  this is too gross for words.
		 */
		if (l->l_flag & LDOMAIN) {
			next->n_flag |= NDOMAIN;
			if (prev->n_flag & NDOMAIN) {
				/*
				 * clean out dots in next->n_name -- they're
				 * no longer valid.  N.B.: we are guaranteed
				 * that next is not an alias
				 */
				hptr = index(next->n_name, '.');
				if (hptr)
					*hptr = 0;
				sprintf(hostbuf, "%s.%s", next->n_name, prev->n_name);
				free(next->n_name);
				next->n_name = strsave(hostbuf);
			}
		} else
			next->n_flag &= ~NDOMAIN;
		next->n_path = strsave(prev->n_path);
		return;
	}

	/* do it by hand instead of sprintf-ing -- foo on '%' */
	if (netdir == LLEFT) {
		/* e.g., host!%s */
		next->n_flag |= HASLEFT;
		strcpy(hostbuf, next->n_name);
		hptr = hostbuf + strlen(hostbuf);
		if (prev->n_flag & NDOMAIN) {
			*hptr++ = '.';
			strcpy(hptr, prev->n_name);
			hptr += strlen(hptr);
		}
		*hptr++ = netchar;
		if (netchar == '%')
			*hptr++ = netchar;
		*hptr++ = '%';
		*hptr++ = 's';
		*hptr = 0;
	} else {
		/* e.g., %s@host, but watch out for the magic @-% conversion */
		next->n_flag |= HASRIGHT;
		if (netchar == '@') {
			next->n_flag |= ATSIGN;
			if (prev->n_flag & ATSIGN)
				netchar = '%';	/* shazam?  shaman? */
		}
		hptr = hostbuf;
		*hptr++ = '%';
		*hptr++ = 's';
		*hptr++ = netchar;
		if (netchar == '%')
			*hptr++ = '%';
		strcpy(hptr, next->n_name);
		if (prev->n_flag & NDOMAIN) {
			hptr += strlen(hptr);
			*hptr++ = '.';
			strcpy(hptr, prev->n_name);
		}
	}
	/* this would be an sprintf were it not for the %'s.  feh. */
	pathprintf(pathbuf, prev->n_path, hostbuf);
	next->n_path = strsave(pathbuf);
}

dehash(n)
node	*n;
{
	if (n->n_flag & DEHASH)
		return;
	Table[Hashpart]->n_tloc = n->n_tloc;
	Table[n->n_tloc] = Table[Hashpart];
	Table[Hashpart] = n;
	n->n_flag |= DEHASH;
	n->n_tloc = Hashpart++;
}

pathprintf(buf, path, host)
char	*buf, *path, *host;
{
	for ( ; *buf = *path; path++) {
		if (*path == '%') {
			switch(path[1]) {
			case 's':
				strcpy(buf, host);
				buf += strlen(buf);
				path++;
				break;
			case '%':
				*++buf = *++path;
				buf++;
				break;
			}
		} else
			buf++;
	}
}

printit(n)
node	*n;
{
	char	*path = n->n_path;
	Cost	cost = n->n_cost;

	for ( ; n; n = n->n_aliaslink) {
		n->n_flag |= MAPPED;
		if (n->n_flag & (NNET | ISPRIVATE | COLLISION))
			continue;
		if (Cflag)
			printf("%ld\t", (long) cost);
		printf("%s\t%s\n", n->n_name, path);
	}
}

//GO.SYSIN DD mapit.c
echo mapaux.c 1>&2
sed 's/^-//' >mapaux.c <<'//GO.SYSIN DD mapaux.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)mapaux.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

void	pack();

void
pack()
{
	int	hole, next;

	/* find first "hole " */
	for (hole = Tabsize - 1; hole >= 0 && Table[hole] != 0; --hole)
		;

	/* repeatedly move next filled entry into last hole */
	for (next = hole - 1; next >= 0; --next) {
		if (Table[next] != 0) {
			Table[hole] = Table[next];
			Table[hole]->n_tloc = hole;
			Table[next] = 0;
			while (Table[--hole] != 0)	/* find next hole */
				;
		}
	}
	Hashpart = hole + 1;
}

amerge()
{
	node	*n, *a;
	int	i;

	for (i = Hashpart; i < Tabsize; i++) {
		n = Table[i];
		if (n == 0)	/* impossible, but ... */
			continue;
		for (a = n->n_alias; a; a = a->n_alias) {
			a->n_link = lmerge(a->n_link, n->n_link);
			n->n_link = 0;
			n = a;
		}
	}
}

STATIC FILE	*Gstream;

dumpgraph()
{
	int	i;
	node	*n;

	if ((Gstream = fopen(Graphout, "w")) == NULL) {
		fprintf(stderr, "%s: ", ProgName);
		perror(Graphout);
	}

	untangle();	/* untangle net cycles for proper output */

	for (i = Hashpart; i < Tabsize; i++) {
		n = Table[i];
		if (n == 0)
			continue;	/* impossible, but ... */
		if (n->n_alias)
			continue;	/* primaries only (wrong?) */
		/* a minor optimization ... */
		if (n->n_link == 0)
			continue;
		/* pathparse doesn't need these */
		if (n->n_flag & NNET)
			continue;
		dumpnode(n);
	}
}

dumpnode(from)
node	*from;
{
	node	*to;
	link	*l;
	char	netbuf[BUFSIZ], *nptr = netbuf;

	for (l = from->n_link ; l; l = l->l_next) {
		to = l->l_to;
		while (to->n_alias)
			to = to->n_alias;	/* get to primary */
		if (from == to)
			continue;	/* oops -- it's me! */

		if ((to->n_flag & NNET) == 0) {
			/* host -> host -- print host>host */
			if (l->l_cost == INF)
				continue;	/* phoney link */
			fputs(from->n_name, Gstream);
			putc('>', Gstream);
			fputs(to->n_name, Gstream);
			putc('\n', Gstream);
		} else {
			/* host -> net -- just cache it for now */
			while (to->n_root && to != to->n_root)
				to = to->n_root;
			*nptr++ = ',';
			strcpy(nptr, to->n_name);
			nptr += strlen(nptr);
		}
	}

	/* dump nets */
	if (nptr != netbuf) {
		/* nets -- print host@\tnet,net, ... */
		*nptr = 0;
		fputs(from->n_name, Gstream);
		putc('@', Gstream);
		*netbuf = '\t';	/* insert tab by killing initial ',' */
		fputs(netbuf, Gstream);	/* skip initial ',' */
		putc('\n', Gstream);
	}
}

/*
 * remove cycles in net definitions. 
 *
 * depth-first search
 *
 * for each net, run dfs on its neighbors (nets only).  if we return to
 * a visited node, that's a net cycle.  mark the cycle with a pointer
 * in the n_root field (which gets us closer to the root of this
 * portion of the dfs tree).
 */
untangle()
{
	int	i;
	node	*n;

	for (i = Hashpart; i < Tabsize; i++) {
		n = Table[i];
		if (n == 0 || (n->n_flag & NNET) == 0 || n->n_root)
			continue;
		dfs(n);
	}
}

dfs(n)
node	*n;
{
	link	*l;
	node	*next;

	n->n_flag |= INDFS;
	n->n_root = n;
	for (l = n->n_link; l; l = l->l_next) {
		next = l->l_to;
		if ((next->n_flag & NNET) == 0)
			continue;
		if ((next->n_flag & INDFS) == 0) {
			dfs(next);
			if (next->n_root != next)
				n->n_root = next->n_root;
		} else
			n->n_root = next->n_root;
	}
	n->n_flag &= ~INDFS;
}

showlinks() 
{
	link	*l;
	node	*n;
	int	i;
	FILE	*estream;

	if ((estream = fopen(Linkout, "w")) == 0)
		return;

	for (i = Hashpart; i < Tabsize; i++) {
		n = Table[i];
		if (n == 0)	/* impossible, but ... */
			continue;
		if (l = n->n_link) {
			fprintf(estream, "%s\t%s(%d)", n->n_name,
				l->l_to->n_name,
				l->l_cost ? l->l_cost : DEFCOST);
			for (l = l->l_next; l; l = l->l_next)
				fprintf(estream, ",\n\t%s(%d)", l->l_to->n_name,
					l->l_cost ? l->l_cost : DEFCOST);
			fputc('\n', estream);
		}
	}
	(void) fclose(estream);
}

//GO.SYSIN DD mapaux.c
echo mem.c 1>&2
sed 's/^-//' >mem.c <<'//GO.SYSIN DD mem.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)mem.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

link	*
newlink()
{
	register link	*rval;

	if ((rval = (link * ) malloc(sizeof(link))) == 0)
		nomem();
	strclear((char *) rval, sizeof(link));	/* fresh as a daisy */
	Lcount++;
	return(rval);
}

node	*
newnode()
{
	register node	*rval;

	if ((rval = (node * ) malloc(sizeof(node))) == 0)
		nomem();
	strclear((char *) rval, sizeof(node));	/* fresh as a daisy */
	Ncount++;
	return(rval);
}

char	*
strsave(s)
register char	*s;
{
	register char *r;

	if ((r = malloc(strlen(s) + 1)) == 0)
		nomem();
	(void) strcpy(r, s);
	return(r);
}

#ifndef strclear
void
strclear(dst, len)
register char *dst;
register int len;
{
	while (--len >= 0)
		*dst++ = 0;
}
#endif strclear

node	**
newtable(size)
register int	size;
{
	register node	**rval;

	if ((rval = (node **) malloc(size * sizeof(*rval))) == 0) 
		nomem();
	strclear((char *) rval, size * sizeof(*rval));
	return(rval);
}

freetable(t, size)
register node	**t;
{
#ifdef MYMALLOC
	addtoheap((char *) t, (long) (size * sizeof(*t)));
#else !MYMALLOC
	free((char *) t);
#endif MYMALLOC
}

nomem()
{
	fprintf(stderr, "%s: Out of memory\n", ProgName);
	badmagic(1);
}

#ifdef MYMALLOC
typedef struct heap heap;
struct heap {
	heap	*h_next;
	long	h_size;
};

STATIC heap	*Heap;	/* not to be confused with a priority queue */

addtoheap(p, size)
register char	*p;
register long	size;
{
	register heap	*hptr = (heap *) p;

	hptr->h_next = Heap;
	hptr->h_size = size;
	Heap = hptr;
}

char	*
mymalloc(n)
register int	n;
{
	static long	size;
	static char	*mem;
	register char	*rval;
	register heap	*hptr;

	if (n > BUFSIZ)
		rval = memget(n);
	else {
#ifdef ALIGN
		int adjustment;

		adjustment = align(mem);
		mem += adjustment;
		size -= adjustment;
#endif ALIGN
		if (n > size) {
			/* look in the heap -- already aligned */
			if (Heap) {
				if (Heap->h_size >= size) {
					mem = (char *) Heap;
					size = Heap->h_size;
					Heap = Heap->h_next;
				} else {
					for (hptr = Heap; hptr->h_next; hptr = hptr->h_next)
						if (hptr->h_next->h_size >= size)
							break;
					if (hptr->h_next) {
						mem = (char *) hptr->h_next;
						size = hptr->h_next->h_size;
						hptr->h_next = hptr->h_next->h_next;
					}
				}
			} else {
				mem = memget(BUFSIZ);
				size = BUFSIZ;
			}
		}
		rval = mem;
		mem += n;
		size -= n;
	}
	return(rval);
}

myfree(s)
char	*s;
{
#ifdef lint
	s = s;
#endif lint
}

#ifdef ALIGN
char	*
memget(n)
register int	n;
{
	register char	*rval;
	register int	adjustment = 0;

	adjustment = align(sbrk(0));
	rval = sbrk(n + adjustment);
	if (rval <= 0)
		return(0);
	return(rval + adjustment);
}

align(n)
register char	*n;
{
	register int	abits;	/* alignment bits */
	register int	adjustment = 0;

	abits = (int) n & ~(0xff << ALIGN) & 0xff;
	if (abits != 0)
		adjustment = (1 << ALIGN) - abits;
	return(adjustment);
}
#endif ALIGN

#endif MYMALLOC
//GO.SYSIN DD mem.c
echo parse.y 1>&2
sed 's/^-//' >parse.y <<'//GO.SYSIN DD parse.y'
%{
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)parse.y	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"

%}

%union {
	node	*y_node;
	Cost	y_cost;
	char	y_net;
	char	*y_name;
	struct {
		node *ys_node;
		Cost ys_cost;
		char ys_net;
		char ys_dir;
	} y_s;
}

%type <y_s> site
%type <y_node> links aliases plist network nlist nsite host
%type <y_cost> cost cexpr
%type <y_net> netchar

%token <y_node>	SITE HOST
%token <y_cost>	COST
%token <y_net>	NET
%token NL PRIVATE

%left	'+' '-'
%left	'*' '/'

%%
map	:	/* empty */
	|	map NL
	|	map links NL
	|	map aliases NL
	|	map network NL
	|	map private NL
	|	error NL
	;

host	:	HOST
	|	PRIVATE	{$$ = addnode("private");}
	;

private	:	PRIVATE '{' {Scanstate = PRIVATING;} plist {Scanstate = OTHER;} '}'
	;

plist	:	SITE		{$1->n_flag |= ISPRIVATE;}
	|	plist ',' SITE	{$3->n_flag |= ISPRIVATE;}
	|	plist ','	/* admit this benign error  */
	;

network	:	host '=' nlist cost
			{fixnet($1, $3, $4, DEFNET, DEFDIR);}
	|	host '=' netchar nlist cost
			{fixnet($1, $4, $5, $3, LRIGHT);}
	|	host '=' nlist netchar cost
			{fixnet($1, $3, $5, $4, LLEFT);}
	;

nlist 	:	'{' nsite '}'	{$$ = $2;}
	;

nsite	:	SITE
	|	nsite ',' SITE {
			/* be careful not to put anything on the list twice */
			if ($3->n_net == 0) {
				$3->n_net = $1;
				$$ = $3;
			}
		}
	|	nsite ','	/* admit this benign error */
	;
		
aliases	:	host '=' SITE		{alias($1, $3);}
	|	aliases ',' SITE	{alias($1, $3);}
	|	aliases ','	/* admit this benign error */
	;

links	:	host site cost {
			addlink($1, $2.ys_node, $3, $2.ys_net, $2.ys_dir);
			/*
			 * give a default path for the return link.
			 * this is wrong, but it's soothes the masses,
			 * who insist on putting error output in the
			 * output.  who said vox populi, vox Dei?
			 */
			addlink($2.ys_node, $1, INF, $2.ys_net, $2.ys_dir);
		}
	|	links ',' site cost {
			addlink($1, $3.ys_node, $4, $3.ys_net, $3.ys_dir);
			/* ditto */
			addlink($3.ys_node, $1, INF, $3.ys_net, $3.ys_dir);
		}
	|	links ','	/* admit this benign error */
	;

site	:	SITE	{
			$$.ys_node = $1;
			$$.ys_net = DEFNET;
			$$.ys_dir = DEFDIR;
		}
	|	netchar SITE	{
			$$.ys_node = $2;
			$$.ys_net = $1;
			$$.ys_dir = LRIGHT;
		}
	|	SITE netchar {
			$$.ys_node = $1;
			$$.ys_net = $2;
			$$.ys_dir = LLEFT;
		}
	;

cost	:	/* empty -- cost is always optional */
			{$$ = DEFCOST;}
	|	'(' {Scanstate = COSTING;} cexpr {Scanstate = OTHER;} ')'
			{$$ = $3;}
	;

cexpr	:	COST
	|	'(' cexpr ')'	{$$ = $2;}
	|	cexpr '+' cexpr	{$$ = $1 + $3;}
	|	cexpr '-' cexpr	{$$ = $1 - $3;}
	|	cexpr '*' cexpr	{$$ = $1 * $3;}
	|	cexpr '/' cexpr	{
			if ($3 == 0)
				yyerror("zero term in divison\n");
			else
				$$ = $1 / $3;
		}
	;

netchar	:	NET		/* normal network operator */
	|	NET NET {	/* for "domains" */
			if ($1 != $2)
				yyerror("invalid domain specifier\n");
			else
				$$=($1 | 0200);
		}
	;
%%

node	*revnetlist();

yyerror(s)
char *s;
{
	/* a concession to bsd error(1) */
	if (Cfile)
		fprintf(stderr, "\"%s\", ", Cfile);
	else
		fprintf(stderr, "%s: ", ProgName);
	fprintf(stderr, "line %d: %s\n", Lineno, s);
}

/*
 * patch in the costs of getting on/off the network.
 *
 * for each network member on netlist, add links:
 *	network -> member	cost = parameter;
 *	member -> network	cost = 0.
 * note that a network can have varying costs to its members, by suitable
 * multiple declarations.  this is a feechur.
 */
fixnet(netnode, netlist, cost, netchar, netdir)
register node	*netnode, *netlist;
Cost	cost;
char	netchar, netdir;
{
	register node	*nextnet;

	netnode->n_flag |= NNET;

	/*
	 * avoid quadratic behavior in addlink(), by reversing net list.
	 * this is cheap, and not necessarily effective, but in practice,
	 * it cuts the cost of addlink() by three.  can you believe that?!?
	 */
	netlist = revnetlist(netlist);

	/* now insert the links */
	for ( ; netlist; netlist = nextnet) {
		/* network -> member */
		(void) addlink(netnode, netlist, cost, netchar, netdir);

		/* member -> network */
		(void) addlink(netlist, netnode, (Cost) 0, netchar, netdir);
		nextnet = netlist->n_net;
		netlist->n_net = 0;	/* clear for later use */
	}
}

STATIC node	*
revnetlist(n)
node	*n;
{
	register node	*pred, *current, *succ;

	if ((pred = n) == 0 || (current = n->n_net) == 0)
		return(n);

	pred->n_net = 0;

	while (current) {
		succ = current->n_net;
		current->n_net = pred;
		pred = current;
		current = succ;
	}
	return(pred);
}
//GO.SYSIN DD parse.y
echo yylex.c 1>&2
sed 's/^-//' >yylex.c <<'//GO.SYSIN DD yylex.c'
#ifndef lint
static char	*sccsid = "@(#)yylex.c	7.1 (down!honey) 85/08/06";
#endif lint

#include "def.h"
#include "y.tab.h"

extern	YYSTYPE yylval;

#define LBRACE '{'
#define RBRACE '}'
#define LPAREN '('
#define RPAREN ')'
#define QUOTE '"'

Cost isacost();

int
yylex()
{
	int	c;
	Cost	cost;
	char	buf[BUFSIZ], errbuf[128];

tailrecursion:
	if (feof(stdin) && yywrap())
		return(EOF);

	if ((c = getchar()) == EOF)
		goto tailrecursion;

	while (c == ' ' || c == '\t')
		c = getchar();

	if (c == '\n') {
		Lineno++;
		c = getchar();
		if (c == ' ' || c == '\t')
			goto tailrecursion;
		ungetc(c, stdin);
		Scanstate = NEWLINE;
		return(NL);
	}

	if (c == '#') {
		while ((c = getchar()) != '\n')
			if (c == EOF)
				goto tailrecursion;
		ungetc(c, stdin);
		goto tailrecursion;
	}

	ungetc(c, stdin);

	switch(Scanstate) {
	case COSTING:
		if (isdigit(c)) {
			cost = 0;
			for (c = getchar(); isdigit(c); c = getchar())
				cost = (cost * 10) + c - '0';

			ungetc(c, stdin);
			yylval.y_cost = cost;
			return(COST);
		}

		
		if (getword(buf) == 0) {
			if ((yylval.y_cost = isacost(buf)) == 0) {
				sprintf(errbuf, "unknown cost (%s), using default", buf);
				yyerror(errbuf);
				yylval.y_cost = DEFCOST;
			}
			return(COST);
		}

		return(getchar());	/* can't be EOF */

	case NEWLINE:
		Scanstate = OTHER;
		if (getword(buf) != 0)
			return(getchar());	/* can't be EOF */
		if (c != '"' && strcmp(buf, "private") == 0)
			return(PRIVATE);

		yylval.y_node = addnode(buf);
		return(HOST);

	case PRIVATING:
		if (getword(buf) == 0) {
			yylval.y_node = addprivate(buf);
			return(SITE);
		}
		return(getchar());	/* can't be EOF */
	}

	if (getword(buf) == 0) {
		yylval.y_node = addnode(buf);
		return(SITE);
	}

	c = getchar();	/* can't be EOF */

	if (index(Netchars, c)) {
		yylval.y_net = c;
		return(NET);
	}

	return(c);
}

getword(str)
char	*str;
{
	int	c;

	c = getchar();
	if (c == QUOTE) {
		for ( ; (*str = getchar()) != '"'; str++) {
			if (*str == '\n') {
				yyerror("newline in quoted string\n");
				ungetc('\n', stdin);
				return(-1);
			}
		}
		*str = 0;
		return(0);
	}

	/* host name must start with alphanumeric or _ */
	if (!isalnum(c) && c != '_') {
		ungetc(c, stdin);
		return(-1);
	}

yymore:
	do {
		*str++ = c;
		c = getchar();
	} while (isalnum(c) || c == '.' || c == '_');

	if (c == '-' && Scanstate != COSTING)
		goto yymore;

	ungetc(c, stdin);
	*str = 0;
	return(0);
}

static struct ctable {
	char *cname;
	Cost cval;
} ctable[] = {
	/*
	 * this list is searched sequentially (with strcmps!).
	 * it is too long.  (they are ordered by frequency of
	 * appearance in a "typical" dataset.)
	 *
	 * adding a 0 cost token breaks isacost().  don't do it.
	 */
	{"DEMAND", 300},
	{"DAILY", 5000},
	{"DIRECT", 200},
	{"EVENING", 1800},
	{"LOCAL", 25},
	{"LOW", 5},	/* baud rate penalty */
	{"HOURLY", 500},
	{"POLLED", 5000},
	{"DEDICATED", 95},
	{"WEEKLY", 30000},
	{"DEAD", INF/2},
	{"HIGH", -5},	/* baud rate bonus */
	/* the remainder are reviled */
	{"ARPA", 100},
	{"DIALED", 300},
	{"A", 300},
	{"B", 500},
	{"C", 1800},
	{"D", 5000},
	{"E", 30000},
	{"F", INF/2},
	0
};

STATIC Cost
isacost(buf)
char	*buf;
{
	struct ctable	*ct;

	for (ct = ctable; ct->cname; ct++)
		if (strcmp(buf, ct->cname) == 0)
			return(ct->cval);

	return((Cost) 0);
}

yywrap()
{
	char	errbuf[100];

	fixprivate();	/* munge private host definitions */

	if (Ifiles == 0) 
		return(1);

	fclose(stdin);
	while (*Ifiles) {
		Lineno = 1;
		Fcnt++;
		if (fopen((Cfile = *Ifiles++), "r"))
			return(0);
		sprintf(errbuf, "%s: %s", ProgName, Cfile);
		perror(errbuf);
	}
	return(1);
}
//GO.SYSIN DD yylex.c
echo makedb.c 1>&2
sed 's/^-//' >makedb.c <<'//GO.SYSIN DD makedb.c'
/* pathalias -- by steve bellovin, as told to peter honeyman */
#ifndef lint
static char	*sccsid = "@(#)makedb.c	7.1 (down!honey) 85/08/06";
#endif lint

#include <stdio.h>
#include "config.h"

typedef struct {
	char *dptr;
	int dsize;
} datum;

char	*Ofile = ALIASDB, *ProgName, *Fname;
int	Nets, Paths, Edges;

char	*strany();

#define USAGE "makedb [-o dbname] [file ...]"

main(argc, argv)
	char	*argv[];
{	int	verbose = 0, append = 0;
	char	*ofptr;
	FILE	*dbstream;

	ProgName = argv[0];
	--argc;
	for ( ; *++argv && **argv == '-'; --argc) {
		(*argv)++;
		switch(**argv) {

		case 'o':	/* dbm output file */
			Ofile = *++argv;
			--argc;
			if (*Ofile == 0) {
				usage();
				exit(1);
			}
			if ((ofptr = rindex(Ofile, '/')) != 0)
				ofptr++;
			else
				ofptr = Ofile;
			if (strlen(ofptr) > 10) {
				ofptr[10] = 0;
				fprintf(stderr, "%s: using %s for db output\n",
					ProgName, Ofile);
			}
			break;

		case 'v':	/* chatty */
			verbose++;
			break;

		case 'a':	/* append mode */
			append++;
			break;

		default:
			fprintf(stderr, "%s: -%s: unknown flag\n", ProgName, *argv);
			usage();
			exit(1);
			break;

		}
	}

	if (append == 0 && dbmfile(Ofile) < 0) {
		perror_(Ofile);
		exit(1);
	}

	if (dbminit(Ofile) < 0) {
		perror_(Ofile);
		exit(1);
	}

	if (argc == 0) {
		makedb(stdin);
	} else {
		while (argc > 0) {
			if ((dbstream = fopen(*argv, "r")) == NULL) {
				perror_(*argv);
			} else {
				Fname = *argv;
				makedb(dbstream);
				fclose(dbstream);
			}
			--argc;
			argv++;
		}
	}
	if (verbose)
		fprintf(stderr, "%d paths, %d edges, %d nets\n", Paths, Edges, Nets);
}

dbmfile(dbmf)
	char	*dbmf;
{	int	fd;
	char	buf[BUFSIZ];

	sprintf(buf, "%s.dir", dbmf);
	fd = creat(buf, 0666);
	if (fd < 0)
		return(-1);
	close(fd);

	sprintf(buf, "%s.pag", dbmf);
	fd = creat(buf, 0666);
	if (fd < 0)
		return(-1);
	close(fd);

	return(0);
}

makedb(stream)
	FILE	*stream;
{	char	*op, line[BUFSIZ], *end;
	datum	key, val;

	/*
	 * keys and values are 0 terminated.  this wastes time and
	 * (disk) space, and is generally stupid.  it does buy
	 * simplicity and backwards compatibility.
	 */
	key.dptr = line;
	while (fgets(line, sizeof(line), stream) != NULL) {
		end = line + strlen(line);
		end[-1] = 0;			/* kill newline */
		op = index(line, '\t');
		if (op != 0) {
			*op++ = 0;
			key.dsize = op - line;		/* 0 terminated */
			val.dptr = op;
			val.dsize = end - op;		/* 0 terminated */
		} else {
			key.dsize = end - line;		/* 0 terminated */
			val.dptr = "\0";		/* why must i do this? */
			val.dsize = 1;
		}
		if (store(key, val) < 0)
			perror_(Ofile);
	}
}

perror_(str)
	char	*str;
{
	fprintf(stderr, "%s: ", ProgName);
	perror(str);
}

usage()
{
	fputs(USAGE, stderr);
}
//GO.SYSIN DD makedb.c
exit