diff --git a/lib/modules/Tools.pmod/Install.pmod b/lib/modules/Tools.pmod/Install.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..2f90c93b094dedbc83e84bc8aeede91b2f86daa7
--- /dev/null
+++ b/lib/modules/Tools.pmod/Install.pmod
@@ -0,0 +1,196 @@
+//
+// Common routines which are useful for various install scripts based on Pike.
+//
+
+string make_absolute_path(string path)
+{
+#if constant(getpwnam)
+  if(sizeof(path) && path[0] == '~')
+  {
+    string user, newpath;
+    sscanf(path, "~%s/%s", user, newpath);
+    
+    if(user && sizeof(user))
+    {
+      array a = getpwnam(user);
+      if(a && sizeof(a) >= 7)
+	return combine_path(a[5], newpath);
+    }
+    
+    return combine_path(getenv("HOME"), path[2..]);
+  }
+#endif
+  
+  if(!sizeof(path) || path[0] != '/')
+    return combine_path(getcwd(), "../", path);
+
+  return path;
+}
+
+class ProgressBar
+{
+  private int width = 45;
+
+  private float phase_base, phase_size;
+  private int max, cur;
+  private string name;
+
+  void set_current(int _cur)
+  {
+    cur = _cur;
+  }
+
+  void set_name(string _name)
+  {
+    name = _name;
+  }
+  
+  void set_phase(float _phase_base, float _phase_size)
+  {
+    phase_base = _phase_base;
+    phase_size = _phase_size;
+  }
+  
+  void update(int increment)
+  {
+    cur += increment;
+    cur = min(cur, max);
+    
+    float ratio = phase_base + ((float)cur/(float)max) * phase_size;
+    if(1.0 < ratio)
+      ratio = 1.0;
+    
+    int bar = (int)(ratio * (float)width);
+    int is_full = (bar == width);
+
+    // int spinner = (max < 2*width ? '=' : ({ '\\', '|', '/', '-' })[cur&3]);
+    int spinner = '=';
+    
+    write("\r   %-13s |%s%c%s%s %4.1f %%  ",
+	  name+":",
+	  "="*bar,
+	  is_full ? '|' : spinner,
+	  is_full ? "" : " "*(width-bar-1),
+	  is_full ? "" : "|",
+	  100.0 * ratio);
+  }
+
+  void create(string _name, int _cur, int _max,
+	      float|void _phase_base, float|void _phase_size)
+    /* NOTE: max must be greater than zero. */
+  {
+    name = _name;
+    max = _max;
+    cur = _cur;
+    
+    phase_base = _phase_base || 0.0;
+    phase_size = _phase_size || 1.0 - phase_base;
+  }
+}
+
+class Readline
+{
+  inherit Stdio.Readline;
+
+  int match_directories_only;
+
+  void trap_signal(int n)
+  {
+    werror("\r\nInterrupted, exit.\r\n");
+    destruct(this_object());
+    exit(1);
+  }
+
+  void destroy()
+  {
+    ::destroy();
+    signal(signum("SIGINT"));
+  }
+
+  static private string low_edit(string data, string|void local_prompt,
+				 array(string)|void attrs)
+  {
+    string r = ::edit(data, local_prompt, (attrs || ({})) | ({ "bold" }));
+    if(!r)
+    {
+      // ^D?
+      werror("\nTerminal closed, exit.\n");
+      destruct(this_object());
+      exit(0);
+    }
+    return r;
+  }
+  
+  string edit(mixed ... args)
+  {
+    return low_edit(@args);
+  }
+  
+  string edit_filename(mixed ... args)
+  {
+    match_directories_only = 0;
+
+    get_input_controller()->bind("^I", file_completion);
+    string s = low_edit(@args);
+    get_input_controller()->unbind("^I");
+    
+    return s;
+  }
+  
+  string edit_directory(mixed ... args)
+  {
+    match_directories_only = 1;
+    
+    get_input_controller()->bind("^I", file_completion);
+    string s = low_edit(@args);
+    get_input_controller()->unbind("^I");
+    
+    return s;
+  }
+  
+  static private string file_completion(string tab)
+  {
+    string text = gettext();
+    int pos = getcursorpos();
+    
+    array(string) path = make_absolute_path(text[..pos-1])/"/";
+    array(string) files =
+      glob(path[-1]+"*",
+	   get_dir(sizeof(path)>1? path[..sizeof(path)-2]*"/"+"/":".")||({}));
+
+    if(match_directories_only)
+      files = Array.filter(files, lambda(string f, string p)
+				  { return (file_stat(p+f)||({0,0}))[1]==-2; },
+			   path[..sizeof(path)-2]*"/"+"/");
+    
+    switch(sizeof(files))
+    {
+    case 0:
+      get_output_controller()->beep();
+      break;
+    case 1:
+      insert(files[0][sizeof(path[-1])..], pos);
+      if((file_stat((path[..sizeof(path)-2]+files)*"/")||({0,0}))[1]==-2)
+	insert("/", getcursorpos());
+      break;
+    default:
+      string pre = String.common_prefix(files)[sizeof(path[-1])..];
+      if(sizeof(pre))
+      {
+	insert(pre, pos);
+      } else {
+	if(!sizeof(path[-1]))
+	  files = Array.filter(files, lambda(string f)
+				      { return !(sizeof(f) && f[0] == '.'); });
+	list_completions(sort(files));
+      }
+      break;
+    }
+  }
+  
+  void create(mixed ... args)
+  {
+    signal(signum("SIGINT"), trap_signal);
+    ::create(@args);
+  }
+}