diff --git a/AUTHORS b/AUTHORS
index 291539d83ced3eae7f352e6523abe35f2488a4e3..3ee49bb78b51002fc1251795a1a2145dd24a14cc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,14 +4,13 @@ design, Ethernet switch elements, user-level pcap/BPF elements
 
 Eddie Kohler
 eddietwo@lcs.mit.edu
-design, core, Linux /proc interface, standard elements, optimizer,
-documentation, other elements
+design, core, Linux /proc interface, standard elements, tools, documentation, 
+distribution, other elements
 
 Robert Morris
 rtm@lcs.mit.edu
-design, Ethernet elements, IP elements, Linux kernel module elements, radio
-elements, Linux kernel patches, IP router configuration, element
-documentation, other elements
+design, Ethernet, IP, Linux kernel module, and radio elements, Linux kernel 
+patches, IP router configuration, element documentation, other elements
 
 Alex Snoeren
 snoeren@lcs.mit.edu
diff --git a/NEWS b/NEWS
index e8a4107611ffc770d5f0cc2d47fe6947c695181d..8229fd7f5caf0b029abdff78136dd0ac2cfd0323 100644
--- a/NEWS
+++ b/NEWS
@@ -2,7 +2,14 @@ Click NEWS
 
 Version 0.52
 
-* Added `--help', `--version', etc. options to tools.
+* Added `--help', `--version', etc. options to tools
+
+* Added "dynamic linking" support to kernel module
+
+* More options for user-level Click, and support for hander invocations on the
+  command line
+
+* Fixed dynamic reconfiguration bug: no more doubled characters
 
 
 Version 0.51   12.Nov.1999
diff --git a/configure.in b/configure.in
index 54fab97d75d50b15d33cff00a96bcf7d438aa794..96e618a0fc25b131e87cb3fd4e0f1bcef879a215 100644
--- a/configure.in
+++ b/configure.in
@@ -15,6 +15,13 @@ AC_PROG_CPP
 AC_PROG_RANLIB
 AC_PROG_INSTALL
 
+dnl
+dnl packagedir
+dnl
+
+packagesdir='$(localstatedir)/packages'
+AC_SUBST(packagesdir)
+
 dnl
 dnl check for C++
 dnl
@@ -203,6 +210,8 @@ dnl
 
 TARGETS=
 AC_SUBST(TARGETS)
+TOOL_TARGETS="click-align click-xform"
+AC_SUBST(TOOL_TARGETS)
 
 dnl check userlevel for pcap library
 
@@ -272,6 +281,7 @@ int main(int c, char **v) {
     ac_cv_cxx_aware_system=no))
   if test $ac_cv_cxx_aware_system = yes; then
     TARGETS="$TARGETS linuxmodule"
+    TOOL_TARGETS="$TOOL_TARGETS click-install"
   else
     AC_MSG_WARN(Your header files must be patched before a C++ program
 can include them. Apply the patch that came with this distribution
@@ -305,7 +315,7 @@ dnl
 dnl Output
 dnl
 
-config_files="Makefile mkelemlist.sh tools/Makefile tools/click-xform/Makefile tools/click-align/Makefile doc/Makefile"
+config_files="Makefile mkelemlist.sh tools/Makefile tools/click-align/Makefile tools/click-install/Makefile tools/click-xform/Makefile doc/Makefile"
 for dir in $POSSIBLE_TARGETS; do
   config_files="$config_files $dir/Makefile"
 done
diff --git a/elements/aqm/red.cc b/elements/aqm/red.cc
index d2d39ed1c4a3a3bde9c6006a1bd08f3c4227ebbb..560ba05e11e572ae1a273f8cfd6a7404a1e1abc3 100644
--- a/elements/aqm/red.cc
+++ b/elements/aqm/red.cc
@@ -232,11 +232,26 @@ RED::read_stats(Element *f, void *)
     ;
 }
 
+String
+RED::read_queues(Element *f, void *)
+{
+  RED *r = (RED *)f;
+  if (r->_queue1)
+    return r->_queue1->id() + "\n";
+  else {
+    String s;
+    for (int i = 0; i < r->_queues.size(); i++)
+      s += r->_queues[i]->id() + "\n";
+    return s;
+  }
+}
+
 void
 RED::add_handlers(HandlerRegistry *fcr)
 {
   fcr->add_read("drops", red_read_drops, 0);
   fcr->add_read("stats", read_stats, 0);
+  fcr->add_read("queues", read_queues, 0);
   fcr->add_read_write("min_thresh", read_parameter, (void *)0,
 		      reconfigure_write_handler, (void *)0);
   fcr->add_read_write("max_thresh", read_parameter, (void *)1,
diff --git a/elements/aqm/red.hh b/elements/aqm/red.hh
index ab2fe2c5566013ecdb6b65c1275ea0d69c7315d4..d626918917df99311b276f5e2fc2ade6a1bae732 100644
--- a/elements/aqm/red.hh
+++ b/elements/aqm/red.hh
@@ -46,6 +46,7 @@ class RED : public Element {
   void set_C1_and_C2();
 
   static String read_stats(Element *, void *);
+  static String read_queues(Element *, void *);
   static String read_parameter(Element *, void *);
   
  public:
diff --git a/elements/standard/classifier.cc b/elements/standard/classifier.cc
index 389cd3697837308efcd0c8074b29377ba00a5ece..db887191b76e3b66ef60beea5b0bc5cc45f9bc6f 100644
--- a/elements/standard/classifier.cc
+++ b/elements/standard/classifier.cc
@@ -710,22 +710,11 @@ Classifier::decompile_string(Element *element, void *)
     char buf[20];
     int offset = e.offset - f->_align_offset;
     sa << i << (offset < 10 ? "   " : "  ") << offset << "/";
-    bool need_mask = 0;
-    for (int j = 0; j < 4; j++) {
-      int m = e.mask.c[j], v = e.value.c[j];
-      for (int k = 0; k < 2; k++, m <<= 4, v <<= 4)
-	if ((m & 0xF0) == 0x00)
-	  sprintf(buf + 2*j + k, "?");
-	else {
-	  sprintf(buf + 2*j + k, "%x", (v >> 4) & 0xF);
-	  if ((m & 0xF0) != 0xF0) need_mask = 1;
-	}
-    }
-    if (need_mask) {
-      sprintf(buf + 8, "%%");
-      for (int j = 0; j < 4; j++)
-	sprintf(buf + 9 + 2*j, "%02x", e.mask.c[j]);
-    }
+    for (int j = 0; j < 4; j++)
+      sprintf(buf + 2*j, "%02x", e.value.c[j]);
+    sprintf(buf + 8, "%%");
+    for (int j = 0; j < 4; j++)
+      sprintf(buf + 9 + 2*j, "%02x", e.mask.c[j]);
     sa << buf << "  yes->";
     if (e.yes <= 0)
       sa << "[" << -e.yes << "]";
diff --git a/elements/standard/red.cc b/elements/standard/red.cc
index d2d39ed1c4a3a3bde9c6006a1bd08f3c4227ebbb..560ba05e11e572ae1a273f8cfd6a7404a1e1abc3 100644
--- a/elements/standard/red.cc
+++ b/elements/standard/red.cc
@@ -232,11 +232,26 @@ RED::read_stats(Element *f, void *)
     ;
 }
 
+String
+RED::read_queues(Element *f, void *)
+{
+  RED *r = (RED *)f;
+  if (r->_queue1)
+    return r->_queue1->id() + "\n";
+  else {
+    String s;
+    for (int i = 0; i < r->_queues.size(); i++)
+      s += r->_queues[i]->id() + "\n";
+    return s;
+  }
+}
+
 void
 RED::add_handlers(HandlerRegistry *fcr)
 {
   fcr->add_read("drops", red_read_drops, 0);
   fcr->add_read("stats", read_stats, 0);
+  fcr->add_read("queues", read_queues, 0);
   fcr->add_read_write("min_thresh", read_parameter, (void *)0,
 		      reconfigure_write_handler, (void *)0);
   fcr->add_read_write("max_thresh", read_parameter, (void *)1,
diff --git a/elements/standard/red.hh b/elements/standard/red.hh
index ab2fe2c5566013ecdb6b65c1275ea0d69c7315d4..d626918917df99311b276f5e2fc2ade6a1bae732 100644
--- a/elements/standard/red.hh
+++ b/elements/standard/red.hh
@@ -46,6 +46,7 @@ class RED : public Element {
   void set_C1_and_C2();
 
   static String read_stats(Element *, void *);
+  static String read_queues(Element *, void *);
   static String read_parameter(Element *, void *);
   
  public:
diff --git a/lib/error.cc b/lib/error.cc
index 248e7d132fa4c7f21ed3d0c90b71f1c97a4c3ba5..69e15e17511c29dd3de7bff9957f4f279b2078d1 100644
--- a/lib/error.cc
+++ b/lib/error.cc
@@ -540,3 +540,33 @@ ContextErrorHandler::vmessage(Seriousness seriousness, const String &message)
   }
   _errh->vmessage(seriousness, _indent + message);
 }
+
+
+//
+// PREFIX ERROR HANDLER
+//
+
+PrefixErrorHandler::PrefixErrorHandler(ErrorHandler *errh,
+				       const String &prefix)
+  : _prefix(prefix), _errh(errh)
+{
+}
+
+void
+PrefixErrorHandler::reset_counts()
+{
+  _errh->reset_counts();
+}
+
+int
+PrefixErrorHandler::verror(Seriousness seriousness, const String &where,
+			   const char *format, va_list val)
+{
+  return _errh->verror(seriousness, _prefix + where, format, val);
+}
+
+void
+PrefixErrorHandler::vmessage(Seriousness seriousness, const String &message)
+{
+  _errh->vmessage(seriousness, _prefix + message);
+}
diff --git a/lib/error.hh b/lib/error.hh
index fe5f619f0334ca24f4b0334f084f799ddf3c91e1..9b7069fdda8ee19e153dd9a87bffba38f18df555 100644
--- a/lib/error.hh
+++ b/lib/error.hh
@@ -82,4 +82,22 @@ class ContextErrorHandler : public ErrorHandler {
   
 };
 
+class PrefixErrorHandler : public ErrorHandler {
+  
+  String _prefix;
+  ErrorHandler *_errh;
+  
+ public:
+  
+  PrefixErrorHandler(ErrorHandler *, const String &prefix);
+  
+  int nwarnings() const			{ return _errh->nwarnings(); }
+  int nerrors() const			{ return _errh->nerrors(); }
+  void reset_counts();
+  
+  int verror(Seriousness, const String &, const char *, va_list);
+  void vmessage(Seriousness, const String &);
+  
+};
+
 #endif
diff --git a/lib/lexer.cc b/lib/lexer.cc
index 1b9951574164943e85af28d00de2252e7498532d..a34c6cd94a60edd4b007d05995e68cc5d3409b6d 100644
--- a/lib/lexer.cc
+++ b/lib/lexer.cc
@@ -513,6 +513,8 @@ Lexer::remove_element_type(int j)
       if (_element_types[i])
 	return;
     _element_types.resize(j);
+    _element_type_names.resize(j);
+    _prev_element_type.resize(j);
   }
 }
 
@@ -522,6 +524,18 @@ Lexer::remove_element_type(const String &name)
   remove_element_type(_element_type_map[name]);
 }
 
+void
+Lexer::element_type_names(Vector<String> &v) const
+{
+  for (int i = FIRST_REAL_TYPE; i < _reset_element_types; i++)
+    if (_element_types[i] && _element_type_map[_element_type_names[i]] == i)
+      v.push_back(_element_type_names[i]);
+  /*  int thunk = 0, value; String key;
+  while (_element_type_map.each(thunk, key, value))
+    if (value >= 0 && value < _reset_element_types && key[0] != '<')
+    v.push_back(key);*/
+}
+
 
 // PORT TUNNELS
 
diff --git a/lib/lexer.hh b/lib/lexer.hh
index 02e24d1bdc6c661d43be5828a748aa0fafd9753d..b52e3d5a71dee320741fbe1759be7793b1885538 100644
--- a/lib/lexer.hh
+++ b/lib/lexer.hh
@@ -126,9 +126,7 @@ class Lexer {
   int force_element_type(String);
   void save_element_types();
 
-  int first_element_type() const	{ return FIRST_REAL_TYPE; }
-  int permanent_element_types() const	{ return _reset_element_types; }
-  Element *element_type(int i) const	{ return _element_types[i]; }
+  void element_type_names(Vector<String> &) const;
   
   void remove_element_type(int);
   void remove_element_type(const String &);
diff --git a/linuxmodule/module.cc b/linuxmodule/module.cc
index 688f1522e75bd87080ed4695ed89b8f538b7320d..00ca7e9c550f32eb55d5b35cf400c9b6c265aaad 100644
--- a/linuxmodule/module.cc
+++ b/linuxmodule/module.cc
@@ -38,7 +38,7 @@ ErrorHandler *kernel_errh = 0;
 static Lexer *lexer = 0;
 Router *current_router = 0;
 
-static Vector<String> provisions;
+static Vector<String> packages;
 
 
 class LinuxModuleLexerSource : public MemoryLexerSource {
@@ -51,8 +51,8 @@ class LinuxModuleLexerSource : public MemoryLexerSource {
 void
 LinuxModuleLexerSource::require(const String &r, ErrorHandler *errh)
 {
-  for (int i = 0; i < provisions.size(); i++)
-    if (provisions[i] == r)
+  for (int i = 0; i < packages.size(); i++)
+    if (packages[i] == r)
       return;
   errh->error("unsatisfied requirement `%s'", String(r).cc());
 }
@@ -73,8 +73,8 @@ initialize_router(const char *data, unsigned len)
   lexer->clear();
   
   if (current_router) {
-    if (current_router->initialize(kernel_errh) >= 0)
-      current_router->print_structure(kernel_errh);
+    current_router->initialize(kernel_errh);
+    // current_router->print_structure(kernel_errh);
     init_router_element_procs();
   }
 }
@@ -268,10 +268,20 @@ read_list(Element *, void *)
 static String
 read_classes(Element *, void *)
 {
-  int nft = lexer->permanent_element_types();
+  Vector<String> v;
+  lexer->element_type_names(v);
   StringAccum sa;
-  for (int i = lexer->first_element_type(); i < nft; i++)
-    sa << lexer->element_type(i)->class_name() << "\n";
+  for (int i = 0; i < v.size(); i++)
+    sa << v[i] << "\n";
+  return sa.take_string();
+}
+
+static String
+read_packages(Element *, void *)
+{
+  StringAccum sa;
+  for (int i = 0; i < packages.size(); i++)
+    sa << packages[i] << "\n";
   return sa.take_string();
 }
 
@@ -314,18 +324,18 @@ extern "C" void
 click_provide(const char *name)
 {
   MOD_INC_USE_COUNT;
-  provisions.push_back(String(name));
+  packages.push_back(String(name));
 }
 
 extern "C" void
 click_unprovide(const char *name)
 {
   String n = name;
-  for (int i = 0; i < provisions.size(); i++)
-    if (provisions[i] == n) {
+  for (int i = 0; i < packages.size(); i++)
+    if (packages[i] == n) {
       MOD_DEC_USE_COUNT;
-      provisions[i] = provisions.back();
-      provisions.pop_back();
+      packages[i] = packages.back();
+      packages.pop_back();
       break;
     }
 }
@@ -334,6 +344,7 @@ extern "C" void
 click_add_element_type(const char *name, Element *e)
 {
   lexer->add_element_type(name, e);
+  lexer->save_element_types();
 }
 
 extern "C" void
@@ -379,6 +390,7 @@ init_module()
   kfr.add_read("flatconfig", read_flatconfig, 0);
   kfr.add_read("list", read_list, 0);
   kfr.add_read("classes", read_classes, 0);
+  kfr.add_read("packages", read_packages, 0);
   kfr.add_read("requirements", read_requirements, 0);
   kfr.add_write("driver", write_driver, 0);
   
diff --git a/tools/Makefile.in b/tools/Makefile.in
index 9fe6768d455dfb74f536b8ce8efdd3d671a2e583..32ac17cae601dd46185fe59b3199a4c08d0584ee 100644
--- a/tools/Makefile.in
+++ b/tools/Makefile.in
@@ -9,29 +9,32 @@ srcdir = @srcdir@
 top_builddir = ..
 subdir = tools
 
-TOOLS = click-xform click-align
+TOOLS = click-align click-install click-xform
+TARGETS = @TOOL_TARGETS@
 
-all: $(TOOLS)
+all: $(TARGETS)
 
-click-xform: Makefile
-	@cd click-xform; $(MAKE) all
 click-align: Makefile
 	@cd click-align; $(MAKE) all
+click-install: Makefile
+	@cd click-install; $(MAKE) all
+click-xform: Makefile
+	@cd click-xform; $(MAKE) all
 
 Makefile: $(srcdir)/Makefile.in
 	cd $(top_builddir) \
 	  && CONFIG_FILES=$(subdir)/$@ CONFIG_ELEMLISTS=no CONFIG_HEADERS= $(SHELL) ./config.status
 
 clean:
-	@-for d in $(TOOLS); do cd $$d; $(MAKE) clean; done
+	@-for d in $(TOOLS); do (cd $$d; $(MAKE) clean); done
 distclean:
-	@-for d in $(TOOLS); do cd $$d; $(MAKE) distclean; done
+	@-for d in $(TOOLS); do (cd $$d; $(MAKE) distclean); done
 	-rm -f Makefile
 
 install:
-	@for d in $(TOOLS); do cd $$d; $(MAKE) install; done
+	@for d in $(TARGETS); do (cd $$d; $(MAKE) install); done
 install-man:
-	@for d in $(TOOLS); do cd $$d; $(MAKE) install-man; done
+	@for d in $(TARGETS); do (cd $$d; $(MAKE) install-man); done
 
 DISTFILES = Makefile.in \
 	lib/elementt.cc lib/elementt.hh \
@@ -41,8 +44,8 @@ DISTFILES = Makefile.in \
 	lib/vectori.cc \
 	click-xform/Makefile.in \
 	click-xform/click-xform.cc \
-	click-flatten/Makefile.in \
-	click-flatten/click-flatten.cc \
+	click-install/Makefile.in \
+	click-install/click-install.cc \
 	click-align/Makefile.in \
 	click-align/click-align.cc \
 	click-align/alignment.cc click-align/alignment.hh \
diff --git a/tools/click-install/.cvsignore b/tools/click-install/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..fc6272aa50b2f6c994336e0581ca6298de760252
--- /dev/null
+++ b/tools/click-install/.cvsignore
@@ -0,0 +1,2 @@
+Makefile *.d
+click-install
diff --git a/tools/click-install/Makefile.in b/tools/click-install/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..710e1cf69ee6096981f1f39fd5eb2cd09c94c1ff
--- /dev/null
+++ b/tools/click-install/Makefile.in
@@ -0,0 +1,86 @@
+SHELL = @SHELL@
+
+srcdir := @srcdir@
+top_srcdir := @top_srcdir@
+top_builddir := ../..
+subdir := tools/click-install
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+libdir = @libdir@
+mandir = @mandir@
+localstatedir = @localstatedir@
+packagesdir = @packagesdir@
+
+VPATH = .:$(top_srcdir)/$(subdir):$(top_srcdir)/tools/lib:$(top_srcdir)/lib
+
+CC = @CC@
+CPP = @CPP@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+INSTALL = @INSTALL@
+mkinstalldirs = @top_srcdir@/mkinstalldirs
+
+.SUFFIXES:
+.SUFFIXES: .S .c .cc .o .s
+
+.c.o:
+	$(COMPILE) -c $<
+.s.o:
+	$(COMPILE) -c $<
+.S.o:
+	$(COMPILE) -c $<
+.cc.o:
+	$(CXXCOMPILE) -c $<
+
+
+OBJS = vectori.o vectorv.o bitvector.o hashmapi.o straccum.o string.o error.o \
+	elementt.o routert.o lexert.o confparse.o clp.o @LIBOBJS@ \
+	click-install.o
+
+CPPFLAGS = @CPPFLAGS@ -MMD -DCLICK_TOOL
+CFLAGS = @CFLAGS@
+CXXFLAGS = @CXXFLAGS@ -fno-exceptions -fno-rtti
+
+DEFS = @DEFS@ -DCLICK_LIBDIR='"$(libdir)"' -DCLICK_PACKAGESDIR='"$(packagesdir)"' \
+	-I. -I$(top_builddir) -I$(srcdir) \
+	-I$(top_srcdir)/tools/lib -I$(top_srcdir)/lib
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+CXXCOMPILE = $(CXX) $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CXXFLAGS)
+CXXLD = $(CXX)
+CXXLINK = $(CXXLD) $(CXXFLAGS) $(LDFLAGS) -o $@
+COMPILE = $(CC) $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(CFLAGS) $(LDFLAGS) -o $@
+
+all: click-install
+
+click-install: Makefile $(OBJS)
+	$(CXXLINK) $(OBJS)
+
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) \
+	  && CONFIG_FILES=$(subdir)/$@ CONFIG_ELEMLISTS=no CONFIG_HEADERS= $(SHELL) ./config.status
+
+DEPFILES := $(wildcard *.d)
+ifneq ($(DEPFILES),)
+include $(DEPFILES)
+endif
+
+install: click-install
+	$(mkinstalldirs) $(sbindir)
+	$(INSTALL) click-install $(sbindir)/click-install
+install-man:
+
+clean:
+	rm -f *.d *.o click-install
+distclean: clean
+	-rm -f Makefile
+
+.PHONY: all clean distclean install install-bin install-man
diff --git a/tools/click-install/click-install.cc b/tools/click-install/click-install.cc
new file mode 100644
index 0000000000000000000000000000000000000000..767049dcac1c3e0aae8331962ee3ade2537eef72
--- /dev/null
+++ b/tools/click-install/click-install.cc
@@ -0,0 +1,284 @@
+/*
+ * click-install.cc -- configuration installer for Click kernel module
+ * Eddie Kohler
+ *
+ * Copyright (c) 1999 Massachusetts Institute of Technology.
+ *
+ * This software is being provided by the copyright holders under the GNU
+ * General Public License, either version 2 or, at your discretion, any later
+ * version. For more information, see the `COPYRIGHT' file in the source
+ * distribution.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "routert.hh"
+#include "lexert.hh"
+#include "error.hh"
+#include "confparse.hh"
+#include "clp.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define HELP_OPT		300
+#define VERSION_OPT		301
+#define ROUTER_OPT		302
+
+static Clp_Option options[] = {
+  { "file", 'f', ROUTER_OPT, Clp_ArgString, 0 },
+  { "help", 'h', HELP_OPT, 0, 0 },
+  { "version", 'v', VERSION_OPT, 0, 0 },
+};
+
+static const char *program_name;
+
+void
+short_usage()
+{
+  fprintf(stderr, "Usage: %s [OPTION]... [ROUTERFILE]\n\
+Try `%s --help' for more information.\n",
+	  program_name, program_name);
+}
+
+void
+usage()
+{
+  printf("\
+`Click-install' installs a Click configuration into the current Linux kernel.\n\
+\n\
+Usage: %s [OPTION]... [ROUTERFILE]\n\
+\n\
+Options:\n\
+  -f, --file FILE               Read router configuration from FILE.\n\
+  -h, --help                    Print this message and exit.\n\
+  -v, --version                 Print version number and exit.\n\
+\n\
+Report bugs to <click@pdos.lcs.mit.edu>.\n", program_name);
+}
+
+static String
+path_find_file_2(const String &filename, String path,
+		 String default_path)
+{
+  while (1) {
+    int colon = path.find_left(':');
+    String dir = (colon < 0 ? path : path.substring(0, colon));
+    if (!dir && default_path) {
+      String s = path_find_file_2(filename, default_path, String());
+      if (s) return s;
+      default_path = String();	// don't search default path twice
+    } else if (dir) {
+      String name = (dir[dir.length()-1] == '/' ? dir + filename : dir + "/" + filename);
+      struct stat s;
+      if (stat(name.cc(), &s) >= 0)
+	return name;
+    }
+    if (colon < 0) return String();
+    path = path.substring(colon + 1);
+  }
+}
+
+static String
+path_find_file(const String &filename, const char *path_variable,
+	       const String &default_path)
+{
+  const char *path = getenv(path_variable);
+  if (!path)
+    return path_find_file_2(filename, default_path, "");
+  else
+    return path_find_file_2(filename, path, default_path);
+}
+
+
+static void
+read_packages(HashMap<String, int> &packages, ErrorHandler *errh)
+{
+  packages.clear();
+  FILE *f = fopen("/proc/click/packages", "r");
+  if (!f)
+    errh->warning("cannot read /proc/click/packages: %s", strerror(errno));
+  else {
+    char buf[1024];
+    while (fgets(buf, 1024, f)) {
+      String p = buf;
+      if (p[p.length()-1] != '\n')
+	errh->warning("/proc/click/packages: line too long");
+      else
+	packages.insert(p.substring(0, -1), 0);
+    }
+    fclose(f);
+  }
+}
+
+RouterT *
+read_router_file(const char *filename, ErrorHandler *errh)
+{
+  FILE *f;
+  if (filename && strcmp(filename, "-") != 0) {
+    f = fopen(filename, "r");
+    if (!f) {
+      errh->error("%s: %s", filename, strerror(errno));
+      return 0;
+    }
+  } else {
+    f = stdin;
+    filename = "<stdin>";
+  }
+  
+  FileLexerTSource lex_source(filename, f);
+  LexerT lexer(errh);
+  lexer.reset(&lex_source);
+  while (lexer.ystatement()) ;
+  RouterT *r = lexer.take_router();
+  
+  if (f != stdin) fclose(f);
+  return r;
+}
+
+int
+main(int argc, char **argv)
+{
+  String::static_initialize();
+  ErrorHandler::static_initialize(new FileErrorHandler(stderr));
+  ErrorHandler *errh = new PrefixErrorHandler
+    (ErrorHandler::default_handler(), "click-install: ");
+
+  // read command line arguments
+  Clp_Parser *clp =
+    Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options);
+  program_name = Clp_ProgramName(clp);
+
+  const char *router_file = 0;
+  
+  while (1) {
+    int opt = Clp_Next(clp);
+    switch (opt) {
+      
+     case HELP_OPT:
+      usage();
+      exit(0);
+      break;
+      
+     case VERSION_OPT:
+      printf("click-install (Click) %s\n", VERSION);
+      printf("Copyright (C) 1999 Massachusetts Institute of Technology\n\
+This is free software; see the source for copying conditions.\n\
+There is NO warranty, not even for merchantability or fitness for a\n\
+particular purpose.\n");
+      exit(0);
+      break;
+      
+     case ROUTER_OPT:
+     case Clp_NotOption:
+      if (router_file) {
+	errh->error("router file specified twice");
+	goto bad_option;
+      }
+      router_file = clp->arg;
+      break;
+
+     bad_option:
+     case Clp_BadOption:
+      short_usage();
+      exit(1);
+      break;
+      
+     case Clp_Done:
+      goto done;
+      
+    }
+  }
+  
+ done:
+  RouterT *r = read_router_file(router_file, errh);
+  if (!r || errh->nerrors() > 0)
+    exit(1);
+  
+  r->flatten(errh);
+
+  // check for Click module; install it if not available
+  {
+    struct stat s;
+    if (stat("/proc/click", &s) < 0) {
+      // try to install module
+      String click_o = path_find_file("click.o", "CLICK_LIB", CLICK_LIBDIR);
+      if (!click_o) {
+	errh->message("cannot find Click module `click.o'");
+	errh->fatal("in CLICK_LIB or `%s'", CLICK_LIBDIR);
+      }
+      String cmdline = "/sbin/insmod " + click_o;
+      (void) system(cmdline);
+      if (stat("/proc/click", &s) < 0)
+	errh->fatal("cannot install Click module");
+    }
+  }
+
+  // find current packages
+  HashMap<String, int> packages(-1);
+  read_packages(packages, errh);
+  
+  // install missing requirements
+  {
+    const HashMap<String, int> &requirements = r->requirement_map();
+    int thunk = 0, value; String key;
+    while (requirements.each(thunk, key, value))
+      if (value >= 0 && packages[key] < 0) {
+	String package = path_find_file
+	  (key + ".o", "CLICK_PACKAGES", CLICK_PACKAGESDIR);
+	if (!package) {
+	  errh->message("cannot find required package `%s.o'", key.cc());
+	  errh->fatal("in CLICK_PACKAGES or `%s'", CLICK_PACKAGESDIR);
+	}
+	String cmdline = "/sbin/insmod " + package;
+	int retval = system(cmdline);
+	if (retval != 0)
+	  errh->fatal("`insmod %s' failed: %s", package.cc(), strerror(errno));
+      }
+  }
+  
+  // write flattened configuration to /proc/click/config
+  FILE *f = fopen("/proc/click/config", "w");
+  if (!f)
+    errh->fatal("cannot install configuration: %s", strerror(errno));
+  String s = r->configuration_string();
+  fputs(s.cc(), f);
+  fclose(f);
+
+  // report errors
+  {
+    char buf[1024];
+    FILE *f = fopen("/proc/click/errors", "r");
+    if (!f)
+      errh->warning("cannot read /proc/click/errors: %s", strerror(errno));
+    else {
+      while (!feof(f)) {
+	size_t s = fread(buf, 1, 1024, f);
+	fwrite(buf, 1, s, stderr);
+      }
+      fclose(f);
+    }
+  }
+
+  // remove unused packages
+  {
+    read_packages(packages, errh);
+    const HashMap<String, int> &requirements = r->requirement_map();
+    int thunk = 0, value; String key;
+    String to_remove;
+    while (packages.each(thunk, key, value))
+      if (value >= 0 && requirements[key] < 0)
+	to_remove += " " + key;
+    if (to_remove) {
+      String cmdline = "/sbin/rmmod " + to_remove + " 2>/dev/null";
+      (void) system(cmdline);
+    }
+  }
+  
+  return 0;
+}
diff --git a/tools/lib/routert.cc b/tools/lib/routert.cc
index f5935a3152c09c15e99718c2372eea526ced45f1..c24a6b0375bc960872fc31ff811c741cad290c55 100644
--- a/tools/lib/routert.cc
+++ b/tools/lib/routert.cc
@@ -175,13 +175,6 @@ RouterT::remove_connection(int i)
 }
 
 
-void
-RouterT::add_requirement(const String &s)
-{
-  _require_map.insert(s, 0);
-}
-
-
 bool
 RouterT::has_connection(const Hookup &hfrom, const Hookup &hto) const
 {
@@ -276,6 +269,13 @@ RouterT::add_tunnel(String in, String out, const String &landmark,
 }
 
 
+void
+RouterT::add_requirement(const String &s)
+{
+  _require_map.insert(s, 0);
+}
+
+
 void
 RouterT::remove_bad_connections()
 {
diff --git a/tools/lib/routert.hh b/tools/lib/routert.hh
index 9dca7f1d04d6bcb5044dc435e100a45e56b6488a..ce3dba3c92ee68a53e5e8f9822c24792d74310ca 100644
--- a/tools/lib/routert.hh
+++ b/tools/lib/routert.hh
@@ -75,6 +75,7 @@ class RouterT : public ElementClassT {
   void remove_connection(int);
 
   void add_requirement(const String &);
+  const HashMap<String, int> &requirement_map() const { return _require_map; }
   
   bool has_connection(const Hookup &, const Hookup &) const;
   void find_connections_from(const Hookup &, Vector<Hookup> &) const;