]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * modules.c: | |
3 | * | |
4 | * Provides functions for dealing with dynamic modules. | |
5 | */ | |
6 | ||
7 | #include <stdlib.h> | |
8 | #include <dlfcn.h> | |
9 | #include "modules.h" | |
10 | #include "../lib/array.h" | |
11 | #include "../lib/sstring.h" | |
12 | #include "../lib/irc_string.h" | |
13 | #include "../lib/splitline.h" | |
14 | #include "../lib/strlfunc.h" | |
15 | #include "../lib/valgrind.h" | |
16 | #include "config.h" | |
17 | #include "error.h" | |
18 | #include <stdio.h> | |
19 | #include <string.h> | |
20 | #include <errno.h> | |
21 | #include <unistd.h> | |
22 | #include "schedule.h" | |
23 | ||
24 | #define DEPFILE "modules.dep" | |
25 | ||
26 | #define MAXMODULES 200 | |
27 | ||
28 | /* Module dependency stuff. | |
29 | * | |
30 | * "parents" are modules we rely on in order to work. | |
31 | * Whenever we load a module we must ensure all parents are present first. | |
32 | * | |
33 | * "children" are modules which rely on us. | |
34 | * Whenever we remove a module, we must ensure all children are removed. | |
35 | * If it's a reload, we will reload them afterwards. | |
36 | * | |
37 | * Now, depmod.pl cunningly arranges the file so that each modules' parents are | |
38 | * always defined earlier in the file. So we can allocate and fill in the parents | |
39 | * array as we read the file. Note that we can also arrange for numchildren to be | |
40 | * correct on each node when we've finished. | |
41 | * | |
42 | * We then do a second pass. At each node, we allocate the space for the | |
43 | * children array and reset numchildren to 0. We can also then populate our | |
44 | * parents' children array with ourselves, using "numchildren" to get the | |
45 | * list in order. | |
46 | * | |
47 | * At the end of the second pass we should have a correctly filled in array! | |
48 | */ | |
49 | ||
50 | sstring *safereload_str; | |
51 | schedule *safereload_sched; | |
52 | ||
53 | struct module_dep { | |
54 | sstring *name; | |
55 | unsigned int numparents; | |
56 | unsigned int numchildren; | |
57 | struct module_dep **parents; | |
58 | struct module_dep **children; | |
59 | unsigned int reloading; | |
60 | }; | |
61 | ||
62 | array modules; | |
63 | ||
64 | struct module_dep moduledeps[MAXMODULES]; | |
65 | unsigned int knownmodules=0; | |
66 | ||
67 | sstring *moddir; | |
68 | sstring *modsuffix; | |
69 | ||
70 | /* Clear out and free the dependencies. */ | |
71 | void clearmoduledeps() { | |
72 | unsigned int i; | |
73 | ||
74 | for (i=0;i<knownmodules;i++) { | |
75 | freesstring(moduledeps[i].name); | |
76 | if (moduledeps[i].parents) { | |
77 | free(moduledeps[i].parents); | |
78 | moduledeps[i].parents=NULL; | |
79 | } | |
80 | if (moduledeps[i].children) { | |
81 | free(moduledeps[i].children); | |
82 | moduledeps[i].children=NULL; | |
83 | } | |
84 | } | |
85 | ||
86 | knownmodules=0; | |
87 | } | |
88 | ||
89 | /* Get a pointer to the given module's dependency record. */ | |
90 | struct module_dep *getmoduledep(const char *name) { | |
91 | unsigned int i; | |
92 | ||
93 | for (i=0;i<knownmodules;i++) { | |
94 | if (!strcmp(name,moduledeps[i].name->content)) | |
95 | return &(moduledeps[i]); | |
96 | } | |
97 | ||
98 | return NULL; | |
99 | } | |
100 | ||
101 | /* Populate the dependency array. Read the monster description above for details. | |
102 | * This relies on moddir being set, so call this after setting it up. */ | |
103 | void initmoduledeps() { | |
104 | FILE *fp; | |
105 | char buf[1024]; | |
106 | char *largv[100]; | |
107 | char largc; | |
108 | char *ch; | |
109 | struct module_dep *mdp, *tmdp; | |
110 | unsigned int i,j; | |
111 | ||
112 | sprintf(buf,"%s/%s",moddir->content,DEPFILE); | |
113 | ||
114 | if (!(fp=fopen(buf,"r"))) { | |
115 | Error("core",ERR_WARNING,"Unable to open module dependency file: %s",buf); | |
116 | } else { | |
117 | /* First pass */ | |
118 | while (!feof(fp)) { | |
119 | fgets(buf, sizeof(buf), fp); | |
120 | if (feof(fp)) | |
121 | break; | |
122 | ||
123 | /* Chomp off that ruddy newline. */ | |
124 | for (ch=buf;*ch;ch++) { | |
125 | if (*ch=='\n' || *ch=='\r') { | |
126 | *ch='\0'; | |
127 | break; | |
128 | } | |
129 | } | |
130 | ||
131 | /* We have a space-delimited list of things. Whatever will we do with that? :) */ | |
132 | largc=splitline(buf,largv,100,0); | |
133 | ||
134 | if (largc<1) | |
135 | continue; | |
136 | ||
137 | /* Add us to the array */ | |
138 | i=knownmodules++; | |
139 | if (i>=MAXMODULES) { | |
140 | Error("core",ERR_ERROR, | |
141 | "Too many modules in dependency file; rebuild with higher MAXMODULES. Module dependencies disabled.\n"); | |
142 | clearmoduledeps(); | |
143 | fclose(fp); | |
144 | return; | |
145 | } | |
146 | ||
147 | mdp=&(moduledeps[i]); | |
148 | ||
149 | mdp->name=getsstring(largv[0],100); | |
150 | mdp->numparents=largc-1; | |
151 | mdp->numchildren=0; /* peace, for now */ | |
152 | mdp->parents=malloc(mdp->numparents * sizeof(struct module_dep *)); | |
153 | ||
154 | /* Fill in the parents array */ | |
155 | for (i=0;i<(largc-1);i++) { | |
156 | if (!(mdp->parents[i]=getmoduledep(largv[i+1]))) { | |
157 | Error("core",ERR_WARNING,"Couldn't find parent module %s of %s. Module dependencies disabled.", | |
158 | largv[i+1],largv[0]); | |
159 | clearmoduledeps(); | |
160 | fclose(fp); | |
161 | return; | |
162 | } | |
163 | mdp->parents[i]->numchildren++; /* break the bad news */ | |
164 | } | |
165 | } | |
166 | ||
167 | fclose(fp); | |
168 | ||
169 | /* Second pass */ | |
170 | for (i=0;i<knownmodules;i++) { | |
171 | mdp=&(moduledeps[i]); | |
172 | ||
173 | /* Allocate child array */ | |
174 | if (mdp->numchildren) { | |
175 | mdp->children=malloc(mdp->numchildren * sizeof(struct module_dep *)); | |
176 | mdp->numchildren=0; /* if only real life were this simple */ | |
177 | } | |
178 | ||
179 | /* Now fill in the children arrays of our parents (bear with me on this) */ | |
180 | for (j=0;j<mdp->numparents;j++) { | |
181 | tmdp=mdp->parents[j]; /* This is just... */ | |
182 | ||
183 | tmdp->children[tmdp->numchildren++]=mdp; /* ... to give this line a chance at making sense */ | |
184 | } | |
185 | } | |
186 | } | |
187 | } | |
188 | ||
189 | void modulerehash() { | |
190 | int i; | |
191 | sstring **mods; | |
192 | array *autoloads; | |
193 | ||
194 | if (moddir!=NULL) | |
195 | freesstring(moddir); | |
196 | ||
197 | if (modsuffix!=NULL) | |
198 | freesstring(modsuffix); | |
199 | ||
200 | clearmoduledeps(); | |
201 | ||
202 | moddir=getcopyconfigitem("core","moduledir",".",100); | |
203 | modsuffix=getcopyconfigitem("core","modulesuffix",".so",5); | |
204 | ||
205 | initmoduledeps(); | |
206 | ||
207 | /* Check for auto-load modules */ | |
208 | autoloads=getconfigitems("core","loadmodule"); | |
209 | if (autoloads!=NULL) { | |
210 | mods=(sstring **)(autoloads->content); | |
211 | for (i=0;i<autoloads->cursi;i++) { | |
212 | insmod(mods[i]->content); | |
213 | } | |
214 | } | |
215 | } | |
216 | ||
217 | void initmodules() { | |
218 | array_init(&modules,sizeof(module)); | |
219 | array_setlim1(&modules,5); | |
220 | array_setlim2(&modules,10); | |
221 | ||
222 | moddir=NULL; | |
223 | modsuffix=NULL; | |
224 | modulerehash(); | |
225 | } | |
226 | ||
227 | int insmod(char *modulename) { | |
228 | int i; | |
229 | module *mods; | |
230 | char buf[1024], modulebuf[1024]; | |
231 | const char *(*verinfo)(const char **); | |
232 | struct module_dep *mdp; | |
233 | ||
234 | strlcpy(modulebuf, modulename, sizeof(modulebuf)); | |
235 | delchars(modulebuf,"./\\;"); | |
236 | ||
237 | if (isloaded(modulebuf)) { | |
238 | Error("core",ERR_DEBUG,"Tried to load already loaded module: %s",modulebuf); | |
239 | return 1; | |
240 | } | |
241 | ||
242 | if (strlen(modulebuf)>100) { | |
243 | Error("core",ERR_WARNING,"Module name too long: %s",modulebuf); | |
244 | return 1; | |
245 | } | |
246 | ||
247 | if ((mdp=getmoduledep(modulebuf))) { | |
248 | for (i=0;i<mdp->numparents;i++) { | |
249 | if (!isloaded(mdp->parents[i]->name->content)) { | |
250 | if (insmod(mdp->parents[i]->name->content)) { | |
251 | Error("core",ERR_WARNING,"Error loading dependant module %s (needed by %s)", | |
252 | mdp->parents[i]->name->content,modulebuf); | |
253 | return 1; | |
254 | } | |
255 | } | |
256 | } | |
257 | } else { | |
258 | Error("core",ERR_WARNING,"Loading module %s without dependency information.",modulebuf); | |
259 | } | |
260 | ||
261 | i=array_getfreeslot(&modules); | |
262 | mods=(module *)(modules.content); | |
263 | ||
264 | sprintf(buf,"%s/%s%s",moddir->content,modulebuf,modsuffix->content); | |
265 | ||
266 | mods[i].handle=dlopen(buf,RTLD_NOW|RTLD_GLOBAL); | |
267 | ||
268 | if(mods[i].handle==NULL) { | |
269 | Error("core",ERR_ERROR,"Loading module %s failed: %s",modulebuf,dlerror()); | |
270 | array_delslot(&modules,i); | |
271 | return -1; | |
272 | } | |
273 | ||
274 | mods[i].name=getsstring(modulebuf,MODULENAMELEN); | |
275 | ||
276 | verinfo=dlsym(mods[i].handle,"_version"); | |
277 | if(verinfo) { | |
278 | mods[i].buildid=verinfo(&mods[i].version); | |
279 | } else { | |
280 | mods[i].version=NULL; | |
281 | mods[i].buildid=NULL; | |
282 | } | |
283 | ||
284 | mods[i].loadedsince = time(NULL); | |
285 | Error("core",ERR_INFO,"Loaded module %s OK.",modulebuf); | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
290 | int getindex(char *modulename) { | |
291 | int i; | |
292 | module *mods; | |
293 | ||
294 | mods=(module *)(modules.content); | |
295 | for(i=0;i<modules.cursi;i++) | |
296 | if (!strcmp(mods[i].name->content,modulename)) | |
297 | return i; | |
298 | ||
299 | return -1; | |
300 | } | |
301 | ||
302 | char *lsmod(int index, const char **ver, const char **buildid, time_t *t) { | |
303 | module *mods; | |
304 | ||
305 | if (index < 0 || index >= modules.cursi) | |
306 | return NULL; | |
307 | ||
308 | mods=(module *)(modules.content); | |
309 | if(ver) | |
310 | *ver=mods[index].version; | |
311 | if(buildid) | |
312 | *buildid=mods[index].buildid; | |
313 | if(t) | |
314 | *t=mods[index].loadedsince; | |
315 | ||
316 | return mods[index].name->content; | |
317 | } | |
318 | ||
319 | int isloaded(char *modulename) { | |
320 | if (getindex(modulename)==-1) | |
321 | return 0; | |
322 | else | |
323 | return 1; | |
324 | } | |
325 | ||
326 | ||
327 | int rmmod(char *modulename, int close) { | |
328 | int i,j; | |
329 | module *mods; | |
330 | struct module_dep *mdp; | |
331 | char modulebuf[1024]; | |
332 | ||
333 | strlcpy(modulebuf, modulename, sizeof(modulebuf)); | |
334 | delchars(modulebuf,"./\\;"); | |
335 | ||
336 | i=getindex(modulebuf); | |
337 | if (i<0) | |
338 | return 1; | |
339 | ||
340 | if ((mdp=getmoduledep(modulebuf))) { | |
341 | for (j=0;j<mdp->numchildren;j++) { | |
342 | if (isloaded(mdp->children[j]->name->content)) { | |
343 | if (rmmod(mdp->children[j]->name->content, close)) { | |
344 | Error("core",ERR_WARNING,"Unable to remove child module %s (depends on %s)", | |
345 | mdp->children[j]->name->content, modulebuf); | |
346 | return 1; | |
347 | } | |
348 | } | |
349 | } | |
350 | ||
351 | /* We may have removed other modules - reaquire the index number in case it has changed. */ | |
352 | i=getindex(modulebuf); | |
353 | if (i<0) | |
354 | return 1; | |
355 | } else { | |
356 | Error("core",ERR_WARNING,"Removing module %s without dependency information",modulebuf); | |
357 | } | |
358 | ||
359 | mods=(module *)(modules.content); | |
360 | ||
361 | if (!close | |
362 | #ifdef BROKEN_DLCLOSE | |
363 | || 1 | |
364 | #endif | |
365 | ) { | |
366 | void (*fini)(); | |
367 | fini = dlsym(mods[i].handle, "__fini"); | |
368 | if(!dlerror()) | |
369 | fini(); | |
370 | } else | |
371 | dlclose(mods[i].handle); | |
372 | ||
373 | freesstring(mods[i].name); | |
374 | array_delslot(&modules,i); | |
375 | ||
376 | Error("core",ERR_INFO,"Removed module %s.",modulebuf); | |
377 | ||
378 | return 0; | |
379 | } | |
380 | ||
381 | /* Set the reload mark on the indicated module, if loaded, and all its | |
382 | * children */ | |
383 | void setreloadmark(struct module_dep *mdp) { | |
384 | unsigned int i; | |
385 | ||
386 | if (!isloaded(mdp->name->content)) | |
387 | return; | |
388 | ||
389 | for (i=0;i<mdp->numchildren;i++) | |
390 | setreloadmark(mdp->children[i]); | |
391 | ||
392 | mdp->reloading=1; | |
393 | } | |
394 | ||
395 | /* preparereload: this function marks in the dependency tree all the | |
396 | * modules which will be unloaded if this one is removed. */ | |
397 | void preparereload(char *modulename) { | |
398 | unsigned int i; | |
399 | struct module_dep *mdp; | |
400 | char modulebuf[1024]; | |
401 | ||
402 | strlcpy(modulebuf, modulename, sizeof(modulebuf)); | |
403 | delchars(modulebuf,"./\\;"); | |
404 | ||
405 | /* First, clear the mark off all dependant modules */ | |
406 | for (i=0;i<knownmodules;i++) | |
407 | moduledeps[i].reloading=0; | |
408 | ||
409 | /* Do nothing if this module is not loaded */ | |
410 | if (getindex(modulebuf)<0) | |
411 | return; | |
412 | ||
413 | if ((mdp=getmoduledep(modulebuf))) { | |
414 | setreloadmark(mdp); | |
415 | } | |
416 | } | |
417 | ||
418 | /* reloadmarked: this function loads all the modules marked for reloading. | |
419 | * The way the array is ordered means we will do this in the "right" order. | |
420 | * This means we won't do an isloaded() check for each one - we shouldn't | |
421 | * get any "already loaded" warnings so let them come out if they happen. | |
422 | */ | |
423 | void reloadmarked(void) { | |
424 | unsigned int i; | |
425 | ||
426 | for (i=0;i<knownmodules;i++) { | |
427 | if (moduledeps[i].reloading) { | |
428 | insmod(moduledeps[i].name->content); | |
429 | } | |
430 | } | |
431 | } | |
432 | ||
433 | void safereloadcallback(void *arg) { | |
434 | safereload_sched=NULL; | |
435 | ||
436 | if (!safereload_str) | |
437 | return; | |
438 | ||
439 | preparereload(safereload_str->content); | |
440 | rmmod(safereload_str->content, 1); | |
441 | insmod(safereload_str->content); | |
442 | reloadmarked(); | |
443 | ||
444 | freesstring(safereload_str); | |
445 | safereload_str=NULL; | |
446 | } | |
447 | ||
448 | void safereload(char *themodule) { | |
449 | if (safereload_str) | |
450 | freesstring(safereload_str); | |
451 | ||
452 | safereload_str=getsstring(themodule, 100); | |
453 | ||
454 | if (safereload_sched) | |
455 | deleteschedule(safereload_sched, safereloadcallback, NULL); | |
456 | ||
457 | scheduleoneshot(1, safereloadcallback, NULL); | |
458 | } | |
459 | ||
460 | void newserv_shutdown() { | |
461 | module *mods; | |
462 | char buf[1024]; | |
463 | ||
464 | while (modules.cursi) { | |
465 | mods=(module *)(modules.content); | |
466 | ||
467 | strlcpy(buf, mods[0].name->content, sizeof(buf)); | |
468 | ||
469 | /* Unload the module unless we're running on Valgrind - | |
470 | * in which case unloading the module would invalidate | |
471 | * stacktraces Valgrind has captured so far. */ | |
472 | rmmod(buf, !RUNNING_ON_VALGRIND); | |
473 | } | |
474 | ||
475 | clearmoduledeps(); | |
476 | ||
477 | if (moddir!=NULL) | |
478 | freesstring(moddir); | |
479 | ||
480 | if (modsuffix!=NULL) | |
481 | freesstring(modsuffix); | |
482 | ||
483 | Error("core",ERR_INFO,"All modules removed. Exiting."); | |
484 | } | |
485 | ||
486 | /* very slow, make sure you cache the pointer! */ | |
487 | void *ndlsym(char *modulename, char *fn) { | |
488 | module *mods=(module *)(modules.content); | |
489 | int i=getindex(modulename); | |
490 | ||
491 | if (i<0) | |
492 | return NULL; | |
493 | ||
494 | return dlsym(mods[i].handle, fn); | |
495 | } |