]> jfr.im git - irc/quakenet/newserv.git/commitdiff
Added module dependency support.
authorsplidge <redacted>
Wed, 30 May 2007 21:09:54 +0000 (22:09 +0100)
committersplidge <redacted>
Wed, 30 May 2007 21:09:54 +0000 (22:09 +0100)
In order for this to work you need to arrange for a modules.dep file to
exist in the same place as your modules.  To create this file, cd to the
module directory and run depmod.pl from there.  Note that the default
Makefile will do this for you on "make install".

Things should fallback gracefully if you lack a modules.dep file (but then
you won't get the benefit of course).

insmod() now automatically loads needed modules first.
rmmod() now automatically removes dependent modules first.
New functions preparereload() and reloadmarked() can be used either side of
a reload to mark dependent modules which are to be removed, and then reload
them, respectively.  control and noperserv's reload methods have been
updated to use these as appropriate.

control/control.c
core/modules.c
core/modules.h
depmod.pl [new file with mode: 0755]
noperserv/noperserv_hooks.c

index bb8b11783d6cca2eae2ba78e4401ca3b09caacfe..aab494525d249f187ed88566838b46b1b71294a3 100644 (file)
@@ -277,9 +277,13 @@ int controllsmod(void *sender, int cargc, char **cargv) {
 int controlreload(void *sender, int cargc, char **cargv) {
   if (cargc<1)
     return CMD_USAGE;
-    
+  
+  preparereload(cargv[0]);
   controlrmmod(sender, cargc, cargv);
-  return controlinsmod(sender, cargc, cargv);
+  controlinsmod(sender, cargc, cargv);
+  reloadmarked();
+  
+  return CMD_OK;
 }
 
 int relink(void *sender, int cargc, char **cargv) {
@@ -500,8 +504,10 @@ void controlspecialreloadmod(void *arg) {
   a->schedule = NULL;
   sstring *froo = a->modulename;
 
+  preparereload(free->content);
   rmmod(froo->content);
   insmod(froo->content);
+  reloadmarked();
   freesstring(froo);
 }
 
index bcb93e4cc5f9d08cd42ed27ff69082a0ddb3f74f..022b9f4c47a56d9f0a27d96b3264da75f0029e7f 100644 (file)
@@ -10,6 +10,7 @@
 #include "../lib/array.h" 
 #include "../lib/sstring.h"
 #include "../lib/irc_string.h"
+#include "../lib/splitline.h"
 #include "config.h"
 #include "error.h"
 #include <stdio.h>
 #include <errno.h>
 #include <unistd.h>
 
+#define DEPFILE        "modules.dep"
+
+#define MAXMODULES 100
+
+/* Module dependency stuff.
+ *
+ * "parents" are modules we rely on in order to work.
+ * Whenever we load a module we must ensure all parents are present first.
+ *
+ * "children" are modules which rely on us.
+ * Whenever we remove a module, we must ensure all children are removed.
+ * If it's a reload, we will reload them afterwards.
+ *
+ * Now, depmod.pl cunningly arranges the file so that each modules' parents are
+ * always defined earlier in the file.  So we can allocate and fill in the parents 
+ * array as we read the file.  Note that we can also arrange for numchildren to be
+ * correct on each node when we've finished.
+ *
+ * We then do a second pass.  At each node, we allocate the space for the
+ * children array and reset numchildren to 0.  We can also then populate our
+ * parents' children array with ourselves, using "numchildren" to get the
+ * list in order.
+ * 
+ * At the end of the second pass we should have a correctly filled in array!
+ */
+
+struct module_dep {
+  sstring *name;
+  unsigned int numparents;
+  unsigned int numchildren;
+  struct module_dep **parents;
+  struct module_dep **children;
+  unsigned int reloading;
+};
+
 array modules;
 
+struct module_dep moduledeps[MAXMODULES];
+unsigned int knownmodules=0;
+
 sstring *moddir;
 sstring *modsuffix;
 
+/* Clear out and free the dependencies. */
+void clearmoduledeps() {
+  unsigned int i;
+  
+  for (i=0;i<knownmodules;i++) {
+    free(moduledeps[i].parents);
+    free(moduledeps[i].children);
+  }
+  
+  knownmodules=0;
+}
+
+/* Get a pointer to the given module's dependency record. */
+struct module_dep *getmoduledep(const char *name) {
+  unsigned int i;
+  
+  for (i=0;i<knownmodules;i++) {
+    if (!strcmp(name,moduledeps[i].name->content))
+      return &(moduledeps[i]);
+  }
+  
+  return NULL;
+}
+
+/* Populate the dependency array.  Read the monster description above for details.
+ * This relies on moddir being set, so call this after setting it up. */
+void initmoduledeps() {
+  FILE *fp;
+  char buf[1024];
+  char *largv[100];
+  char largc;
+  char *ch;
+  struct module_dep *mdp, *tmdp, *mdps;
+  unsigned int i,j;
+  
+  sprintf(buf,"%s/%s",moddir->content,DEPFILE);
+  
+  if (!(fp=fopen(buf,"r"))) {
+    Error("core",ERR_WARNING,"Unable to open module dependency file: %s",buf);
+  } else {
+    /* First pass */
+    while (!feof(fp)) {
+      fgets(buf, sizeof(buf), fp);
+      if (feof(fp))
+        break;
+      
+      /* Chomp off that ruddy newline. */
+      for (ch=buf;*ch;ch++) {
+        if (*ch=='\n' || *ch=='\r') {
+          *ch='\0';
+          break;
+        }
+      }
+        
+      /* We have a space-delimited list of things.  Whatever will we do with that? :) */
+      largc=splitline(buf,largv,100,0);
+      
+      if (largc<1)
+        continue;
+      
+      /* Add us to the array */
+      i=knownmodules++;
+      if (i>=MAXMODULES) {
+        Error("core",ERR_FATAL,"Too many modules in dependency file; rebuild with higher MAXMODULES.\n");
+        return;
+      }
+      
+      mdp=&(moduledeps[i]);
+      
+      mdp->name=getsstring(largv[0],100);
+      mdp->numparents=largc-1;
+      mdp->numchildren=0; /* peace, for now */
+      mdp->parents=malloc(mdp->numparents * sizeof(struct module_dep *));
+      
+      /* Fill in the parents array */
+      for (i=0;i<(largc-1);i++) {
+        if (!(mdp->parents[i]=getmoduledep(largv[i+1]))) {
+          Error("core",ERR_WARNING,"Couldn't find parent module %s of %s.",largv[i+1],largv[0]);
+          continue;
+        }
+        mdp->parents[i]->numchildren++; /* break the bad news */
+      }
+    }
+    
+    /* Second pass */
+    for (i=0;i<knownmodules;i++) {
+      mdp=&(moduledeps[i]);
+      
+      /* Allocate child array */
+      if (mdp->numchildren) {
+        mdp->children=malloc(mdp->numchildren * sizeof(struct module_dep *));
+        mdp->numchildren=0; /* if only real life were this simple */
+      }
+      
+      /* Now fill in the children arrays of our parents (bear with me on this) */
+      for (j=0;j<mdp->numparents;j++) {
+        tmdp=mdp->parents[j]; /* This is just... */
+        
+        tmdp->children[tmdp->numchildren++]=mdp; /* ... to give this line a chance at making sense */
+      }
+    }
+  }
+}
+
 void modulerehash() {
   int i;
   sstring **mods;
@@ -32,10 +175,14 @@ void modulerehash() {
   
   if (modsuffix!=NULL)
     freesstring(modsuffix);
+
+  clearmoduledeps();
   
   moddir=getcopyconfigitem("core","moduledir",".",100);
   modsuffix=getcopyconfigitem("core","modulesuffix",".so",5);  
 
+  initmoduledeps();
+
   /* Check for auto-load modules */
   autoloads=getconfigitems("core","loadmodule");
   if (autoloads!=NULL) {
@@ -61,6 +208,7 @@ int insmod(char *modulename) {
   module *mods;
   char buf[1024];
   const char *(*verinfo)(void);
+  struct module_dep *mdp;
 
   delchars(modulename,"./\\;");
   
@@ -73,10 +221,24 @@ int insmod(char *modulename) {
     Error("core",ERR_WARNING,"Module name too long: %s",modulename);  
     return 1;
   }
-  
+
+  if ((mdp=getmoduledep(modulename))) {
+    for (i=0;i<mdp->numparents;i++) {
+      if (!isloaded(mdp->parents[i]->name->content)) {
+        if (insmod(mdp->parents[i]->name->content)) {
+          Error("core",ERR_WARNING,"Error loading dependant module %s (needed by %s)",
+                   mdp->parents[i]->name->content,modulename);
+          return 1;
+        }
+      }
+    }
+  } else {
+    Error("core",ERR_WARNING,"Loading module %s without dependency information.",modulename);
+  }
+
   i=array_getfreeslot(&modules);
   mods=(module *)(modules.content);
-  
+
   sprintf(buf,"%s/%s%s",moddir->content,modulename,modsuffix->content);
   
   mods[i].handle=dlopen(buf,RTLD_NOW|RTLD_GLOBAL);
@@ -86,7 +248,7 @@ int insmod(char *modulename) {
     array_delslot(&modules,i);
     return -1;
   }
-  
+
   mods[i].name=getsstring(modulename,MODULENAMELEN);
 
   verinfo=dlsym(mods[i].handle,"_version");
@@ -140,15 +302,36 @@ int isloaded(char *modulename) {
     return 1;
 }
 
+
 int rmmod(char *modulename) {
-  int i;
+  int i,j;
   module *mods;
+  struct module_dep *mdp;
   
   delchars(modulename,"./\\;");
   
   i=getindex(modulename);
   if (i<0)
     return 1;
+
+  if ((mdp=getmoduledep(modulename))) {
+    for (j=0;j<mdp->numchildren;j++) {
+      if (isloaded(mdp->children[j]->name->content)) {
+        if (rmmod(mdp->children[j]->name->content)) {
+          Error("core",ERR_WARNING,"Unable to remove child module %s (depends on %s)",
+                 mdp->children[j]->name->content, modulename);
+          return 1;
+        }
+      }
+    }
+
+    /* We may have removed other modules - reaquire the index number in case it has changed. */
+    i=getindex(modulename);
+    if (i<0)
+      return 1;
+  } else {
+    Error("core",ERR_WARNING,"Removing module %s without dependency information",modulename);
+  }
   
   mods=(module *)(modules.content);
     
@@ -169,3 +352,54 @@ int rmmod(char *modulename) {
   
   return 0;
 }    
+
+/* Set the reload mark on the indicated module, if loaded, and all its
+ * children */
+void setreloadmark(struct module_dep *mdp) {
+  unsigned int i;
+  
+  if (!isloaded(mdp->name->content))
+    return;
+  
+  for (i=0;i<mdp->numchildren;i++) 
+    setreloadmark(mdp->children[i]);
+  
+  mdp->reloading=1;
+}
+
+/* preparereload: this function marks in the dependency tree all the 
+ * modules which will be unloaded if this one is removed. */
+void preparereload(char *modulename) {
+  unsigned int i;
+  struct module_dep *mdp;
+  
+  delchars(modulename,"./\\;");
+  
+  /* First, clear the mark off all dependant modules */
+  for (i=0;i<knownmodules;i++)
+    moduledeps[i].reloading=0;
+
+  /* Do nothing if this module is not loaded */
+  i=getindex(modulename);
+  if (i<0)
+    return;
+    
+  if ((mdp=getmoduledep(modulename))) {
+    setreloadmark(mdp);
+  }
+}
+
+/* reloadmarked: this function loads all the modules marked for reloading. 
+ * The way the array is ordered means we will do this in the "right" order. 
+ * This means we won't do an isloaded() check for each one - we shouldn't
+ * get any "already loaded" warnings so let them come out if they happen.
+ */
+void reloadmarked(void) {
+  unsigned int i;
+  
+  for (i=0;i<knownmodules;i++) {
+    if (moduledeps[i].reloading) {
+      insmod(moduledeps[i].name->content);
+    }
+  }
+}
index 52c2d10d0db1bde8abb7b052b602f77c3287d13c..4dcbbd4001e488a29fe9e26c637c8cf83c02669c 100644 (file)
@@ -21,5 +21,7 @@ int isloaded(char *modulename);
 int rmmod(char *modulename);
 char *lsmod(int index);
 const char *lsmodver(int index);
+void preparereload(char *modulename);
+void reloadmarked(void);
 
 #endif
diff --git a/depmod.pl b/depmod.pl
new file mode 100755 (executable)
index 0000000..119b74c
--- /dev/null
+++ b/depmod.pl
@@ -0,0 +1,93 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my %definers;
+my %reqsyms;
+my %reqmods;
+my %printed;
+
+my @arglist = <*.so>;
+#push @arglist, "../newserv";
+my @realarglist;
+
+open DEPENDS,">modules.dep";
+open GRAPH,">modgraph.dot";
+
+for (@arglist) {
+  my $modname=$_;
+  open NM,"nm $modname |";
+  
+  $modname =~ s/.so$//;
+  
+  push @realarglist, $modname;
+  
+  $reqsyms{$modname} = [];
+  
+  while (<NM>) {
+    chomp;
+    next unless (/([URTB]) (\S+)/);
+
+    my ($type,$sym) = ($1, $2);
+    
+    next if ($sym eq "_init");
+    next if ($sym eq "_fini");
+    next if ($sym eq "_version");
+
+    if ($type eq "U") {
+      push @{$reqsyms{$modname}}, $sym;
+    } else {
+      if (defined $definers{$sym}) {
+        print "Error: Multiple modules are providing $sym, at least $modname and $definers{$sym}\n";
+      } else {
+        $definers{$sym}=$modname;
+      }
+    }
+  }
+  
+  close NM;
+}
+
+print GRAPH "digraph g { \n";
+
+for (@realarglist) {
+  my $modname=$_;
+  
+  $reqmods{$modname} = {};
+  
+  for (@{$reqsyms{$modname}}) {
+    my $provider = $definers{$_};
+    
+    if (defined $provider) {
+      ${reqmods{$modname}}{$provider}=1;
+    }
+  }
+}
+
+for (@realarglist) {
+  printdep($_);
+}
+
+print GRAPH "}\n";
+
+close GRAPH;
+close DEPENDS;
+
+sub printdep {
+  my ($modname) = @_;
+  
+  if (!(defined $printed{$modname})) {
+    $printed{$modname}=1;
+    
+    for (keys %{$reqmods{$modname}}) {
+      printdep($_);
+    }
+
+    print DEPENDS "$modname";
+    for (keys %{$reqmods{$modname}}) {
+      print DEPENDS " $_";
+      print GRAPH "\t\"$modname\" -> \"$_\";\n";
+    }
+    print DEPENDS "\n";
+  }
+}
index a47f796f24a9c8de11f5fff4c75772ed8a92e13d..6d6f5b245d29e4321915a51d23d3e0df9c14ca38 100644 (file)
@@ -187,10 +187,6 @@ int noperserv_specialmod(nick *np, char *command, ScheduleCallback reloadhandler
   }
 
   if(!strcmp(cargv[0], "noperserv")) {
-    if(noperserv_modules_loaded("noperserv_*")) {
-      controlreply(np, "NOT UNLOADING. Unload all dependencies first.");
-      return CMD_ERROR;
-    }
     if(special.schedule) {
       controlreply(np, "Previous attempt at un/reload still in progress.");
       return CMD_OK;