/* Released into the public domain Jan 2002 by Noah Gibbs.
   Everything in this file is in the public domain, and is
   released without warranty, guarantee, or even the implication
   of marketability or fitness for a particular purpose.
   If you start a war in Asia with this, don't come crying to
   me. */

/* Thanks go to Geir Harald Hansen, who released an objectd well
   before I knew anything about DGD.  Studying his work has been
   valuable, as has correspondence (his and others) on the DGD Mailing
   List.  This objectd has somewhat different goals than his, and
   operates in some very different (and some very similar) ways.  See
   also the doc/design/OBJECTD document for details. */

#include <kernel/kernel.h>
#include <kernel/objreg.h>
#include <kernel/rsrc.h>
#include <kernel/user.h>

#include <phantasmal/log.h>
#include <phantasmal/lpc_names.h>

#include <status.h>
#include <type.h>
#include <trace.h>

/* TODO:  (features)
   - Allow recompiles that destruct and rebuild all necessary stuff
     - By specifying a list of objects to (re)compile
     - By specifying a list of files that changed
       - Allow to add manually to dependency list?
       - Compile list automatically by time, or just specify time?
*/


inherit objreg API_OBJREG;
inherit rsrc API_RSRC;


/* With aggro_recompile when a parent is found that we don't have an
   issue for, we'll recompile it.  That'll keep all the issue IDs the
   same and stuff, but means we'll have an issue for it.
*/
private int     aggro_recompile;

/* Keep track of recomp_paths and all_libs to see what to recompile
   during init's aggressive recompile.  Untracked_libs is the set of
   libraries that existed during ObjectD's init, so there's one
   untracked issue of each to be removed. */
private mapping recomp_paths, all_libs, untracked_libs;

/* For compiling(), include(), compiled_failed(), etc we need to
   track files.  This mechanism attempts to track what's being
   compiled. */
private string last_file;
private mixed* comp_dep;

/* Used when initializing objectd */
private int setup_done;

/* Should be private so other objects can't get objects by issue --
   that'll be important for security reasons.  These heavy arrays should
   have absolutely no exported references anywhere outside this object,
   ever. */
private object obj_issues;

/* This mapping contains objects whose latest issue is destroyed.  It
   is indexed by object name. */
private mapping dest_issues;

/* Array of objects to upgrade -- normally there should be no more than
   one object here at a time unless a previous call_out has failed... */
private object* upgrade_clonables;
private int     upgrade_callout;

/* This object has a path_special() method.  ObjectD defers to it, if
   it has been set. */
private object path_special_object;

private void   unregister_inherit_data(object issue);
private void   register_inherit_data(object issue);
private void   call_upgraded(object obj);
private object add_clonable(string owner, object obj, string* inherited);
private object add_lib(string owner, string path, string* inherited);
        string destroyed_obj_list(void);
private void   suspend_system(void);
private void   release_system(void);


/* These objects are used for tracking issues of objects */
#define LIB_LWO      "/usr/common/data/lib_issue"
#define CLONABLE_LWO "/usr/common/data/clonable_issue"


static void create(varargs int clone)
{
  object test;

  objreg::create();
  rsrc::create();

  if(clone) {
    error("Attempting to clone objectd!");
  }

  /* Make a new heavy array to hold the object issues */
  if(!find_object(HEAVY_ARRAY))
    compile_object(HEAVY_ARRAY);

  obj_issues = clone_object(HEAVY_ARRAY);
  dest_issues = ([ ]);
  setup_done = 0;

  aggro_recompile = 0;
  recomp_paths = ([ ]);
  all_libs = ([ ]);
  comp_dep = ({ });
  upgrade_clonables = ({ });

  if(!find_object(ISSUE_LWO))
    compile_object(ISSUE_LWO);
  if(!find_object(CLONABLE_LWO))
    compile_object(CLONABLE_LWO);
  if(!find_object(LIB_LWO))
    compile_object(LIB_LWO);
}

void destructed(int clone) {
  if(!SYSTEM())
    return;

  destruct_object(obj_issues);
}


/*** Private funcs ***/

/* Used during init.  Recompile all libs we know only by path (not issue
   struct) to get an issue for them. */
private void recompile_to_track_libs(mapping fixups) {
  while(map_sizeof(recomp_paths) > 0) {
    int    ctr;
    mixed* keys;

    keys = map_indices(recomp_paths);
    for(ctr = 0; ctr < sizeof(keys); ctr++) {
      /* Make sure this gets an entry in the fixups array so we
	 know to fix up the reference to the string afterward. */
      if(!fixups[keys[ctr]]) fixups[keys[ctr]] = ({ });
      fixups[keys[ctr]] += recomp_paths[keys[ctr]];
      recomp_paths[keys[ctr]] = nil;

      /* Destroy & Recompile lib */
      destruct_object(keys[ctr]);
      compile_object(keys[ctr]);
    }
  }
}

/* Used during init.  We've recompiled all the libs, now we need to
   make sure that stuff compiled before had issue objects for the libs
   have the right things in their parent arrays. */
/* TODO: add fixup to child arrays?  Or make obsolete? */
private void fix_parent_arrays(mapping fixups) {
  mixed* keys;
  int    ctr;

  keys = map_indices(fixups);
  for(ctr = 0; ctr < sizeof(keys); ctr++) {
    int    ctr2;
    mixed* paths;
    int    lib_index;

    /* For each library to fix up... */
    lib_index = status(keys[ctr])[O_INDEX];

    paths = fixups[keys[ctr]];
    for(ctr2 = 0; ctr2 < sizeof(paths); ctr2++) {
      string path;
      object obj, issue;
      int    ctr3;
      mixed* inh;

      /* For each file to fix the lib for... */
      path = paths[ctr2];
      obj = find_object(path);
      if(obj) {
	issue = obj_issues->index(status(obj)[O_INDEX]);
      } else {
	issue = obj_issues->index(status(path)[O_INDEX]);
      }

      /* Replace the string with the issue in the parent array */
      if(!issue) {
	LOGD->write_syslog("Can't get parent issue for " + path, LOG_ERR);
	continue;
      }
      inh = issue->get_parents();
      for(ctr3 = 0; ctr3 < sizeof(inh); ctr3++) {
	if(inh[ctr3] == keys[ctr]) {
	  inh[ctr3] = lib_index;
	}
      }
    }
  }
}

/* Called during init to fix up child arrays and clear old issues out
   of prev pointers. */
private void fix_child_arrays(string* owners) {
  mixed* keys;
  int    ctr, status_idx;
  object issue, index, first;
  string owner;

  keys = map_indices(all_libs);
  for(ctr = 0; ctr < sizeof(keys); ctr++) {
    issue = obj_issues->index(all_libs[keys[ctr]]);
    if(!issue) {
      LOGD->write_syslog("Can't find issue for index " + all_libs[keys[ctr]],
			 LOG_ERR_FATAL);
      error("Can't find issue for index " + all_libs[keys[ctr]]);
    }
    issue->clear_prev();
    issue->clear_children();
  }
  for(ctr = 0; ctr < sizeof(keys); ctr++) {
    issue = obj_issues->index(all_libs[keys[ctr]]);
    if(!issue) {
      LOGD->write_syslog("Can't find issue for index " + all_libs[keys[ctr]],
			 LOG_ERR_FATAL);
      error("Can't find issue for index " + all_libs[keys[ctr]]);
    }
    register_inherit_data(issue);
  }

  /* There are two objects in the Kernel Library - TELNET_CONN and
     BINARY_CONN - that ObjRegD does not list. */
  status_idx = status(find_object(TELNET_CONN))[O_INDEX];
  issue = obj_issues->index(status_idx);
  register_inherit_data(issue);

  status_idx = status(find_object(BINARY_CONN))[O_INDEX];
  issue = obj_issues->index(status_idx);
  register_inherit_data(issue);

  /* Iterate through all owners to make sure prev fields are clear and
     to register their inherit data. */
  for(ctr = 0; ctr < sizeof(owners); ctr++) {
    owner = owners[ctr];

    first = objreg::first_link(owner);
    if(!first) continue;  /* No objects for this owner... */

    index = first;
    do {
      status_idx = status(index)[O_INDEX];
      issue = obj_issues->index(status_idx);
      if(!issue || issue->get_prev() != -1) {
	LOGD->write_syslog("Can't verify issue in get_child_arrays!",
			   LOG_ERR_FATAL);
	error("Internal error!");
      }
      register_inherit_data(issue);

      index = objreg::next_link(index);
    } while(index != first);
  }
}

/* Used during init.  Recompile every clonable object */
private void recompile_every_clonable(string* owners) {
  string  owner;
  int     ctr, ctr2;
  object  index, first;
  object* obj_arr;

  /* These are special objects that ObjRegD doesn't contain. */
  compile_object(TELNET_CONN);
  compile_object(BINARY_CONN);

  /* First, for all owners recompile all owned (non-lib) objects. */
  for(ctr = 0; ctr < sizeof(owners); ctr++) {
    owner = owners[ctr];

    first = objreg::first_link(owner);
    if(!first) continue;  /* No objects for this owner... */

    /* Build simple linear object array from circular list in
       objregd */
    obj_arr = ({ first });
    index = objreg::next_link(first);
    while(index != first) {
      obj_arr += ({ index });
      index = objreg::next_link(index);
    }

    /* For this owner, recompile all his objects so we can get their
       dependencies */
    for(ctr2 = 0; ctr2 < sizeof(obj_arr); ctr2++) {
      string name, path;

      name = object_name(obj_arr[ctr2]);

      /* Skip all clones -- we're just getting dependencies */
      if(sscanf(name, "%*s#%*d"))
	continue;

      compile_object(name);
    }
  }
}

/* Used during init.  Recompile every clonable so we know the issue
   info for each one.  While we're doing it, collect inheritance info
   so we know what they inherit from.  Then, recompile all libraries
   to track them properly. */
private void recompile_every_object(string* owners) {
  mapping fixups;
  string *lib_names;
  int     ctr;

  /* set aggressive recompiles -- need to find all libraries */
  aggro_recompile = 1;

  /* Recompile all the clonables so we can get a first set of libs */
  recompile_every_clonable(owners);

  /* Now recompile all the libs we found while compiling but haven't started
     tracking yet */
  fixups = ([ ]);
  recompile_to_track_libs(fixups);

  fix_parent_arrays(fixups);
  /* Now all_libs exists */

  /* Set up untracked_libs */
  lib_names = map_indices(all_libs);
  untracked_libs = ([ ]);
  for(ctr = 0; ctr < sizeof(lib_names); ctr++) {
    LOGD->write_syslog("Adding " + lib_names[ctr] + " to untracked_libs",
		       LOG_VERBOSE);
    untracked_libs[lib_names[ctr]] = 1;
  }

  fix_child_arrays(owners);

  /* Add faked AUTO object */
  add_lib("System", AUTO, ({ }));

  /* Set aggro_recompile mode to strong checking -- anything that
     would initiate an aggressive recompile before should be outlawed
     now since we should already know everything we need to know about
     all objects.  This also turns off recomp_paths registration, so
     we can get rid of it. */
  aggro_recompile = 2;
  recomp_paths = nil;
}

/* Used during init.  Objregd keeps track of all clones, so we can count
   them pretty easily. */
private void count_clones(string* owners) {
  string  owner;
  int     ctr, ctr2, ctr3;
  object  index, first;
  object* obj_arr;
  mapping lib_issues;

  lib_issues = ([ ]);

  /* Now, for all owners go through all objects */
  for(ctr = 0; ctr < sizeof(owners); ctr++) {
    owner = owners[ctr];

    first = objreg::first_link(owner);
    if(!first) continue;  /* No objects for this owner... */

    /* Build simple linear object array from circular list in
       objregd */
    obj_arr = ({ first });
    index = objreg::next_link(first);
    while(index != first) {
      obj_arr += ({ index });
      index = objreg::next_link(index);
    }

    /* For this owner, enumerate all objects */
    for(ctr2 = 0; ctr2 < sizeof(obj_arr); ctr2++) {
      string name, path;
      int    index;
      object issue;
      mixed* inh;

      name = object_name(obj_arr[ctr2]);
      if(sscanf(name, "%s#%*d", path)) {

	/* For clones, add to clonables */
	index = status(find_object(path))[O_INDEX];
	issue = obj_issues->index(index);
	issue->add_clone(obj_arr[ctr2]);
      }
    }
  }
}

/* This removes this issue from the child lists of everybody it
   used to inherit from */
private void unregister_inherit_data(object issue) {
  mixed* parents;
  int    ctr, index;
  object inh_issue;

  index = issue->get_index();
  parents = issue->get_parents();
  for(ctr = 0; ctr < sizeof(parents); ctr++) {
    if(typeof(parents[ctr]) == T_STRING) {
      if(aggro_recompile > 1) {
	LOGD->write_syslog("Uncorrected parent string '" + parents[ctr]
			   + "'!", LOG_ERR);
      }
    } else {
      inh_issue = obj_issues->index(parents[ctr]);
      if(!inh_issue) {
	LOGD->write_syslog("Internal error (" + issue->get_name()
			   + "), Parent: " + parents[ctr]
			   + ", Child: " + index, LOG_ERR_FATAL);
	error("Internal error!");
      }

      inh_issue->remove_child(index);
    }
  }
}

/* This removes this issue from the parent lists of everybody that
   used to inherit from it.  Currently only used in remove_program. */
private void clear_child_data(object issue) {
  mixed* children;
  int    ctr, index;
  object inh_issue;

  index = issue->get_index();
  children = issue->get_children();
  if(!children) {
    LOGD->write_syslog("Should children be NULL for issue #" +
		       index + " in clear_child_data?",
		       LOG_WARNING);
    return;
  }
  for(ctr = 0; ctr < sizeof(children); ctr++) {
    inh_issue = obj_issues->index(children[ctr]);
    if(!inh_issue) {
      LOGD->write_syslog("Internal error, Child: " + children[ctr]
			 + ", Parent: " + index, LOG_ERR_FATAL);
      error("Internal error!");
    }

    inh_issue->remove_parent(index);
  }
}

/* This adds the issue to the child lists of everybody it now
   inherits from. */
private void register_inherit_data(object issue) {
  mixed* parents;
  int    ctr, index;
  object tmp;

  index = issue->get_index();
  parents = issue->get_parents();
  for(ctr = 0; ctr < sizeof(parents); ctr++) {
    if(typeof(parents[ctr]) == T_STRING) {
      if(aggro_recompile > 1) {
	LOGD->write_syslog("Uncorrected parent string! [2]", LOG_ERR);
      }
    } else if(typeof(parents[ctr]) == T_NIL) {
      LOGD->write_syslog("Parents[" + ctr + "] is nil for issue #" + index
			 + "!", LOG_ERR);
    } else {
      tmp = obj_issues->index(parents[ctr]);
      if(!tmp) {
	LOGD->write_syslog("Can't get parent issue from index!", LOG_ERR);
      } else {
	tmp->add_child(index);
      }
    }
  }
}

/* Transfer clones from an old object issue to a new one */
private void transfer_clones(object old_issue, object new_issue) {
  if(old_issue->get_num_clones() > 0) {
    new_issue->clones_from(old_issue);
  }

  if(old_issue->get_prev() != -1) {
    transfer_clones(obj_issues->index(old_issue->get_prev()), new_issue);
  }
}

private void convert_inherited_str_to_mixed(string *inherited, mixed* inh_obj,
					    string path) {
  int    tmp_idx, ctr;
  object tmp_issue;

  /* Convert inherited (an array of strings) to inh_obj (an array of
     objects) */
  for(ctr = 0; ctr < sizeof(inherited); ctr++) {
    /* Get latest issue of obj inherited[ctr] */
    tmp_idx = status(inherited[ctr])[O_INDEX];
    tmp_issue = obj_issues->index(tmp_idx);

    if(tmp_issue) {
      inh_obj[ctr] = tmp_idx;

      /* During init or later */
      if(aggro_recompile == 1) {
	all_libs[inherited[ctr]] = tmp_idx;
      }
    } else {
      if(aggro_recompile > 1) {
	LOGD->write_syslog("Unrecognized lib " + inherited[ctr]
			   + " with strong recompile check on!",
			   LOG_ERR);
      }
      if(aggro_recompile == 1) {
	if(!recomp_paths[inherited[ctr]]) recomp_paths[inherited[ctr]] = ({ });
	recomp_paths[inherited[ctr]] += ({ path });
      }
      inh_obj[ctr] = inherited[ctr];
    }
  }
}

private object add_clonable(string owner, object obj, string* inherited) {
  object new_issue, old_version, tmp;
  int    idx, ctr, old_index;
  mixed* inh_obj;

  if(!obj) {
    LOGD->write_syslog("Nil object passed to add_clonable!", LOG_ERR);
    return nil;
  }

  if(sscanf(object_name(obj), "%*s#%*d")) {
    LOGD->write_syslog("Clone passed to add_clonable!", LOG_ERR);
    return nil;
  }

  inh_obj = allocate(sizeof(inherited));
  convert_inherited_str_to_mixed(inherited, inh_obj, object_name(obj));

  idx = status(obj)[O_INDEX];

  old_index = -1;
  old_version = obj_issues->index(idx);
  if(old_version) {
    old_index = idx;
  } else if(dest_issues[object_name(obj)]) {
    old_index = dest_issues[object_name(obj)];
    old_version = obj_issues->index(old_index);
    if(!old_version) {
      LOGD->write_syslog("Can't get issue# for destroyed version!", LOG_ERR);
    }
    if(old_version && !old_version->destroyed()) {
      LOGD->write_syslog("Old issue is in dest_issues but not destroyed!",
			 LOG_WARN);
    }
  }

  if(old_version && !old_version->destroyed()) {
    /* Recompile - owner, obj, idx, and old_version stay the
       same, but inheritance may have changed. */

    unregister_inherit_data(old_version);

    old_version->set_parents(inh_obj);
    register_inherit_data(old_version);

    LOGD->write_syslog("Upgrading object on recompile", LOG_VERBOSE);

    call_upgraded(find_object(object_name(obj)));
  } else {
    /* new object or old one was destructed */
    new_issue = new_object(CLONABLE_LWO);

    /* The clones will all be updated at the end of this thread, so
       we don't actually need a previous version around. */
    new_issue->set_vals(owner, obj, idx, inh_obj, comp_dep);
    comp_dep = nil;

    obj_issues->set_index(idx, new_issue);
    register_inherit_data(new_issue);

    /* When we recompile this, it'll upgrade the old clones to the new
       version -- so the destroyed one should go away. */
    if(old_version) {
      /* Transfer clones over */
      transfer_clones(old_version, new_issue);

      /* Remove from dest array */
      dest_issues[object_name(obj)] = nil;
      dest_issues[old_index] = nil;
    }

  }

  /* If we're making a new object, that means that this issue# shouldn't
     be in the destructed issues. */
  if(dest_issues[idx]) {
    LOGD->write_syslog("Clearing destructed issue# which should be clean!",
		       LOG_WARN);
  }
  dest_issues[idx] = nil;

  return new_issue;
}

private object add_lib(string owner, string path, string* inherited) {
  object new_issue, tmp_obj, old_version;
  int    idx, ctr, tmp_idx, old_index;
  mixed* inh_obj;

  inh_obj = allocate(sizeof(inherited));
  convert_inherited_str_to_mixed(inherited, inh_obj, path);

  idx = status(path)[O_INDEX];
  all_libs[path] = idx;
  old_index = -1;
  old_version = obj_issues->index(idx);
  if(old_version) {
    old_index = idx;
  } else if(dest_issues[path]) {
    old_index = dest_issues[path];
    old_version = obj_issues->index(old_index);
    if(!old_version) {
      LOGD->write_syslog("Can't get issue for old_version adding lib!",
			 LOG_ERR);
    }
  }

  if(old_version && !old_version->destroyed()) {
    /* Recompile - owner, path, idx, old_version and mod_count stay the
       same, but inheritance may have changed. */

    unregister_inherit_data(old_version);

    old_version->set_parents(inh_obj);
    register_inherit_data(old_version);
  } else {
    /* Brand new, or old issue was destructed */
    new_issue = new_object(LIB_LWO);
    /* If old one was destructed then give it as the prev version and
       set the mod count one higher than the old one */
    new_issue->set_vals(owner, path, idx, inh_obj,
			comp_dep, old_index);
    comp_dep = nil;

    obj_issues->set_index(idx, new_issue);
    register_inherit_data(new_issue);

    /* If we have a prev version then it's kept track of by the new
       issue so we can remove it from dest_issues. */
    if(old_version) {
      dest_issues[path] = nil;
      dest_issues[old_index] = nil;
      dest_issues[idx] = nil;
    }
  }

  return new_issue;
}


private void call_upgraded(object obj) {

  if(!obj) {
    LOGD->write_syslog("Calling call_upgraded on nil!", LOG_ERR);
    return;
  }

  /* Originally we checked to see if the object had an "upgraded"
     function and if so, we scheduled a callout.  That doesn't work
     because the object hasn't been recompiled yet so it may have just
     gotten or lost an "upgraded" function.  So we just schedule the
     callout and *then* see if it has that function. */
  upgrade_clonables += ({ obj });

  if(!upgrade_callout) {
    suspend_system();
    upgrade_callout = call_out("do_upgrade", 0.0, obj);
    if(upgrade_callout <= 0) {
      release_system();
      LOGD->write_syslog("Error scheduling upgrade call_out for "
			 + object_name(obj) + "!");
    }
  }
}

static void do_upgrade(object obj) {
  int ctr;

  upgrade_callout = 0;

  /* Give objects more time to call upgraded() if they need it since
     they may be rereading large files all at once.  Currently this
     doesn't vary per user. */
  rsrc::set_rsrc("upgrade ticks",
		 rsrc::query_rsrc("ticks")[RSRC_MAX] * 4,
		 0, 0);

  rlimits(status()[ST_STACKDEPTH]; -1) {
    for(ctr = 0; ctr < sizeof(upgrade_clonables); ctr++) {
      if(function_object("upgraded", upgrade_clonables[ctr])) {
	catch {
	  rlimits(status()[ST_STACKDEPTH];
		  rsrc::query_rsrc("upgrade ticks")[RSRC_MAX]) {
	    call_other(upgrade_clonables[ctr], "upgraded");
	  }
	} : {
	  LOGD->write_syslog("Error in " + object_name(upgrade_clonables[ctr])
			     + "->upgraded()! (error text already in log)",
			     LOG_ERR);
	}

      }
    }
  }

  release_system();
  upgrade_clonables = ({ });
}


/*** Hook funcs called after this object is set_object_manager'd ***/

/* Non-lib object has just been compiled - called just before create() */
void compile(string owner, object obj, string * source,
	     string inherited...)
{
  if(previous_program() == DRIVER) {
    LOGD->write_syslog("compile: " + object_name(obj)
		       + ", issue #" + status(obj)[O_INDEX], LOG_VERBOSE);

    add_clonable(owner, obj, inherited);
  }
}

/* Inheritable object has just been compiled */
void compile_lib(string owner, string path, string * source,
		 string inherited...)
{
  if(previous_program() == DRIVER) {
    LOGD->write_syslog("compile_lib: " + path + " ("
		       + status(path)[O_INDEX] + ")", LOG_VERBOSE);

    add_lib(owner, path, inherited);
  }
}


/* Object has just been cloned - called just before create(1) */
void clone(string owner, object obj)
{
  if(previous_program() == DRIVER) {
    int index;
    object issue;

    LOGD->write_syslog("clone: " + object_name(obj), LOG_VERBOSE);

    index = status(obj)[O_INDEX];
    issue = obj_issues->index(index);
    issue->add_clone(obj);

    if(issue->destroyed() && aggro_recompile > 1)
      LOGD->write_syslog("Clone of destroyed object...  Odd!", LOG_WARN);
  }
}

/* Object (non-lib) is about to be destructed */
void destruct(string owner, object obj)
{
  if(previous_program() == DRIVER) { 
    int    index;
    object issue;
    string objname;

    LOGD->write_syslog("destruct: " + object_name(obj)
		       + ", issue #" + status(obj)[O_INDEX], LOG_VERBOSE);

    index = status(obj)[O_INDEX];
    issue = obj_issues->index(index);

    if(sscanf(object_name(obj), "%s#%*d", objname)) {
      if(issue)
	issue->destroy_clone(obj);

      if(function_object("destructed",obj)) {
	obj->destructed(1);
      }

      return;
    }

    objname = object_name(obj);

    /* Not a clone */
    if(dest_issues[objname] || dest_issues[index]) {
      if(dest_issues[objname])
	LOGD->write_syslog("Name '" + STRINGD->mixed_sprint(objname)
			   + "' already in dest_issues.",
			   LOG_VERBOSE);
      if(dest_issues[index])
	LOGD->write_syslog("Index " + index + " already in dest_issues.",
			   LOG_VERBOSE);

      LOGD->write_syslog("Object is already in dest_issues!", LOG_WARN);
    }

    /* Put into dest_issues */
    dest_issues[objname] = index;
    dest_issues[index] = index;

    /* Mark issue destroyed */
    if(issue)
      issue->destruct();

    if(function_object("destructed", obj)) {
      obj->destructed(0);
    }

  }
}

/* Inheritable object is about to be destructed */
void destruct_lib(string owner, string path)
{
  if(previous_program() == DRIVER) {
    object issue;
    int    index;

    index = status(path)[O_INDEX];
    issue = obj_issues->index(index);

    LOGD->write_syslog("destruct_lib: " + path + " (" + index + ")",
		       LOG_VERBOSE);

    all_libs[path] = nil;

    /* If the Obj is not registered, that's fine unless we've
       aggro_recompile'd and set stronger checking on. */
    if(!issue && aggro_recompile > 1) {
      LOGD->write_syslog("Can't get issue for lib "
				+ path + " in destruct_lib!", LOG_WARN);
    }
    if(!issue) return;

    if(dest_issues[path] || dest_issues[index]) {
      LOGD->write_syslog("Lib is already in dest_issues!", LOG_ERR);
    }

    dest_issues[path] = index;
    dest_issues[index] = index;

    if(issue)
      issue->destruct();
  }
}

/* Last ref to specific issue removed */
void remove_program(string owner, string path, int timestamp, int index)
{
  if(previous_program() == DRIVER) {
    object issue, cur;
    mixed* status;

    LOGD->write_syslog("remove: " + path + ", issue #" + index, LOG_VERBOSE);

    /* Get current version */
    cur = find_object(path);
    status = status(cur ? cur : path);
    if(status) {
      cur = obj_issues->index(status[O_INDEX]);
    } else {
      if(dest_issues[path]) {
	cur = obj_issues->index(dest_issues[path]);
	if(!cur)
	  LOGD->write_syslog("No current instance of " + path + "!", LOG_ERR);
      }
    }

    if(!cur) {
      LOGD->write_syslog("Removing issueless stray " + path, LOG_ERR);
    }

    issue = obj_issues->index(index);
    if(issue) {
      if(issue->destroyed()) {
	dest_issues[index] = nil;
	dest_issues[path] = nil;
      }
    } else if(dest_issues[path] || dest_issues[index]) {
      LOGD->write_syslog("Issue in dest_issues but not obj_issues!", LOG_ERR);
    }

    if(issue) {
      unregister_inherit_data(issue);

      /* For libraries only, clear child data */
      if(function_object("get_children", issue))
	clear_child_data(issue);
    } else if(aggro_recompile > 1) {
      /* This is either a mistake in ObjectD's tracking or an issue
	 of a library from ObjectD's initialization. */
      if(untracked_libs && untracked_libs[path]) {
	untracked_libs[path]--;
	if(untracked_libs[path] <= 0) {
	  untracked_libs[path] = nil;
	}
      } else
	LOGD->write_syslog("Removing unregistered issue of " + path, LOG_WARN);
    }

    obj_issues->set_index(index, nil);

    /* If no current issue is to be found, this no longer
       belongs in all_libs */
    if(!status(path)) {
      all_libs[path] = nil;
    }

  }
}


/* Object (possibly a lib) is about to be compiled */
void compiling(string path)
{
  if(previous_program() == DRIVER) {
    object obj, issue;
    int    recomp;

    LOGD->write_syslog("compiling: " + path, LOG_VERBOSE);

    /* Set up entry to keep track of objects being compiled */
    last_file = path;
    comp_dep = ({ });

    /* Call upgrading() func to let object know it's being compiled */
    obj = find_object(path);
    if(obj && function_object("upgrading", obj)) {
      catch {
	obj->upgrading();
      } : {
	LOGD->write_syslog("Error in " + object_name(obj) + "->upgrading()",
			   LOG_ERR);
      }
    }
  }
}

/* 'path' about to be included */
void include(string from, string path)
{
  if(previous_program() == DRIVER && aggro_recompile > 0) {
    string trimmed_from;

    LOGD->write_syslog("include " + path + " from " + from,
		       LOG_ULTRA_VERBOSE);

    if(path != "AUTO" && path != "/include/AUTO" && path != nil
       && path != ""
       && sscanf(path, "%*s\.h") == 0)
      LOGD->write_syslog("Including non-header file '" + path + "'", LOG_ERR);

    if(!comp_dep) comp_dep = ({});
    comp_dep += ({ path });
  }
}

/* An attempt to compile the given object (including libs) has failed */
void compile_failed(string owner, string path)
{
  if(previous_program() == DRIVER) {
    comp_dep = nil;

    LOGD->write_syslog("compile_failed: " + path, LOG_VERBOSE);
  }
}

/* Non-System files can inherit from a nonstandard AUTO object if the
   ObjectD returns one.  This is where that happens. */
/* We check the auto_objects mapping for directories that have been
   registered for special treatement. */
string path_special(string compiled)
{
  if(path_special_object) {
    string tmp;
    tmp = path_special_object->path_special(compiled);
    return tmp;
  }

  return nil;
}

/* Return true if path is not a legal first arg for call_other */
int forbid_call(string path)
{
  if(previous_program() == DRIVER) {
  }
  return 0;
}

/* Return true to forbid 'from' from inheriting 'path', privately or no
   according to 'priv' */
int forbid_inherit(string from, string path, int priv)
{
  if(previous_program() == DRIVER) {
    LOGD->write_syslog("forbid_inherit: " + path + " from " + from,
		       LOG_ULTRA_VERBOSE);

    /* If we *did* actually forbid something, that would be logged with
       something other than LOG_ULTRA_VERBOSE... */
  }
  return 0;
}

/* Return the file or strings for an include file. */
mixed include_file(string compiled, string from, string path)
{
  string result;
  if(previous_program() == DRIVER) {
    LOGD->write_syslog("include_file: " + path + " from " + from + ", program "+compiled,
                       LOG_ULTRA_VERBOSE);

    if (path == "/include/AUTO" && from == "/include/std.h" && 
        DRIVER->creator(compiled) != "System" && path_special_object) {
        /*
         * special object-dependent include file
         */
        result = path_special(compiled);
        if (result) {
            return result;
        }
    } 
    return path;
  }
}



/*** API Funcs called by the system at large ***/

/* Security is handled here by allowing the setup only once, and the
   fact that the operation gives information only to this object. */
void do_initial_obj_setup(void) {
  string* owners;

  /* For object iteration: */
  object  index, first;
  object* obj_arr;

  if(!SYSTEM())
    return;

  /* This just makes sure that do_initial_obj_setup() is only called once */
  if(setup_done) return;
  setup_done = 1;

  owners = rsrc::query_owners();

  recompile_every_object(owners);
  count_clones(owners);
}

/* Gives a list of destroyed object names */
string destroyed_obj_list(void) {
  mixed*  keys;
  int     ctr, idx;
  object  issue;
  string  ret;

  if(!SYSTEM())
    return nil;

  ret = "Objects:\n";
  keys = map_indices(dest_issues);
  for(ctr = 0; ctr < sizeof(keys); ctr++) {
    if(typeof(keys[ctr]) == T_INT) {
      /* Index is num, not name */
      continue;
    }

    ret += "* ";
    idx = dest_issues[keys[ctr]];
    issue = obj_issues->index(idx);
    if(issue) {
      ret += issue->get_name();
    } else {
      ret += "(nil)";
    }
    ret += " (" + idx + ")" + "\n";
  }

  return ret;
}

/* This is secure because it exports only a string.  You can find out
   how many clones an object has, but you can't get their object
   pointers. */
string report_on_object(string spec) {
  object issue;
  string ret;
  int    ctr, lib;
  mixed* arr, status;
  mixed  index;

  if(!SYSTEM())
    return nil;

  if(sscanf(spec, "#%d", index) || sscanf(spec, "%d", index)) {
    issue = obj_issues->index(index);

    if(!issue && dest_issues[index]) {
      LOGD->write_syslog("Issue is in dest_issues but not obj_issues!",
			 LOG_ERR);
    }

    if(!issue)
      return "Issue #" + index + " isn't tracked by objectd\n";

  } else {
    status = status(spec);
    if(status) {
      index = status[O_INDEX];
      issue = obj_issues->index(index);
      if(!issue && !find_object(spec))
	return "DGD tracks " + spec + " (#" + index
	  + ") but objectd does not.\n";
    }
    if(find_object(spec)) {
      index = status(find_object(spec))[O_INDEX];
      issue = obj_issues->index(index);
      if(!issue)
	return "DGD recognizes obj " + spec + " but objectd does not.\n";
    } else if(!issue) {
      if(dest_issues[spec]) {
	issue = obj_issues->index(dest_issues[spec]);
      } else
	return "DGD doesn't recognize object " + spec + "\n";
    }
  }

  ret = "";

  if(issue->destroyed() && dest_issues[issue->get_index()])
    ret += "Destroyed ";
  else if (issue->destroyed() || dest_issues[issue->get_index()]) {
    if(dest_issues[issue->get_index()]) {
      ret += "(In Dest array)\n";
    } else {
      ret += "(Marked destroyed)\n";
    }
    ret += "Incorrectly destroyed ";
  }

  if(object_name(issue) == (LIB_LWO + "#-1")) {
    ret += "Inheritable\n";
    lib = 1;
  } else if(object_name(issue) == (CLONABLE_LWO + "#-1")) {
    ret += "Clonable\n";
    ret += "  " + issue->get_num_clones() + " clones exist\n";
  } else {
    return "Internal error!";
  }

  if(typeof(issue->get_name()) != T_STRING) {
    error("Issue name isn't a string...");
  }
  ret += "Name: " + issue->get_name() + "\n";
  ret += "Index: " + issue->get_index() + "\n";
  ret += "Comp_time: " + ctime(issue->get_comp_time()) + "\n";
  if(issue->get_prev() != -1) {
    ret += "Previous issue: " + issue->get_prev() + "\n";
  } else {
    ret += "No previous issue\n";
  }

  ret += "Inherits from: ";
  arr = issue->get_parents();
  if(!arr) arr = ({ });
  for(ctr = 0; ctr < sizeof(arr); ctr++) {
    index = arr[ctr];
    if(typeof(index) == T_STRING)
      ret += index + " ";
    else
      ret += "#" + index + " ";
  }
  ret += "\n";

  if(lib) {
    ret += "Its children are: ";
    arr = issue->get_children();
    if(!arr) arr = ({ });
    for(ctr = 0; ctr < sizeof(arr); ctr++) {
      index = arr[ctr];
      ret += "#" + index + " ";
    }
    ret += "\n";
  }

  ret += "Also depends on: ";
  arr = issue->get_dependencies();
  if(!arr) arr = ({ });
  for(ctr = 0; ctr < sizeof(arr); ctr++) {
    index = arr[ctr];
    if(typeof(index) == T_STRING)
      ret += index + " ";
    else
      error("Non-string dependency!");
  }
  ret += "\n";

  return ret;
}

/*
  Suspend_system suspends network input, new logins and callouts
  except in this object.  (idea stolen from Geir Harald Hansen's
  ObjectD).  This will need to be copied to any and every object
  that suspends callouts -- the RSRCD checks previous_object()
  to find out who *isn't* suspended.  TelnetD only suspends
  new incoming network activity.
*/
private void suspend_system() {
  if(SYSTEM()) {
    RSRCD->suspend_callouts();
    TELNETD->suspend_input(0);  /* 0 means "not shutdown" */
  } else
    error("Only privileged code can call suspend_system!");
}

/*
  Releases everything that suspend_system suspends.
*/
private void release_system() {
  if(SYSTEM()) {
    RSRCD->release_callouts();
    TELNETD->release_input();
  } else
    error("Only privileged code can call release_system!");
}


/* Returns a status report on an object (passed in directly or
   by path, object name or issue number).  The report is in
   the following format (offsets 0 to 6):

   ({ issue number (T_INT),
      object name (T_STRING),
      parent array (T_ARRAY) or nil,
      child array (T_ARRAY) or nil,
      num_clones (T_INT) or nil,
      previous issue number (T_INT) or nil,
      object destroyed or not (0 or 1, T_INT)
    })
*/
mixed* od_status(mixed obj_spec) {
  object issue;
  int    index;

  if(!SYSTEM())
    return nil;

  if(typeof(obj_spec) == T_INT) {
    index = obj_spec;
    issue = obj_issues->index(obj_spec);
  } else if(typeof(obj_spec) == T_OBJECT) {
    index = status(obj_spec)[O_INDEX];
    issue = obj_issues->index(obj_spec);
  } else if(typeof(obj_spec) == T_STRING) {
    if(find_object(obj_spec)) {
      index = status(find_object(obj_spec))[O_INDEX];
    } else {
      if(status(obj_spec)) {
	index = status(obj_spec)[O_INDEX];
      } else {
	return nil;
      }
    }
    issue = obj_issues->index(index);
  }

  if(!issue) return nil;

  return ({ index, issue->get_name(), issue->get_parents(),
	      issue->get_children(), issue->get_num_clones(),
	      issue->get_prev() != -1 ? issue->get_prev() : nil,
	      issue->destroyed() });
}

/* Recompiles all objects other than DRIVER.  Output is sent to object
  output using the "message" function.  The user object happens to work
  well with this :-) */
void recompile_auto_object(object output) {
  int    ctr, ctr2;
  mixed* keys, *didnt_dest;

  if(!SYSTEM())
    return;

  /* This will always be logged at this level */
  LOGD->write_syslog("Doing full rebuild...", LOG_NORMAL);

  if(!SYSTEM())
    error("Can't call recompile_auto_object unprivileged!");

  /* Destruct all libs... */
  keys = map_indices(all_libs);
  didnt_dest = ({ });
  ctr2 = 0;
  for(ctr = 0; ctr < sizeof(keys); ctr++) {
    if(status(keys[ctr])) {
      ctr2++;
      destruct_object(keys[ctr]);
    } else {
      didnt_dest += ({ keys[ctr] });
    }
  }
  ctr2++;
  destruct_object(AUTO);

  if(ctr2 != sizeof(keys) + 1) {
    /* In this case, some elements of all_libs are already destructed,
       which violates our assumptions.  Of course, the rebuild will
       probably fix it. */
    LOGD->write_syslog("Couldn't destruct " + sizeof(didnt_dest) + "/"
		       + (sizeof(keys) + 1) + " libs: "
		       + implode(didnt_dest, ", "), LOG_WARN);
  }

  /* Recompile all clonables */
  recompile_every_clonable(rsrc::query_owners());
}


void set_path_special(object new_manager) {
  DRIVER->message("set_path_special " + object_name(new_manager)+" by "+previous_program()+"\n");
  if(SYSTEM()) {
    path_special_object = new_manager;
  } else {
    error("Only System objects can set the path_special object!");
  }
}
