1 #include "first.h" 2 3 #include "plugin.h" 4 #include "base.h" 5 #include "array.h" 6 #include "log.h" 7 8 #include <string.h> 9 #include <stdlib.h> 10 11 array plugin_stats; /* global */ 12 13 #ifdef HAVE_VALGRIND_VALGRIND_H 14 # include <valgrind/valgrind.h> 15 #endif 16 17 #if !defined(__WIN32) && !defined(LIGHTTPD_STATIC) 18 # include <dlfcn.h> 19 #endif 20 /* 21 * 22 * if you change this enum to add a new callback, be sure 23 * - that PLUGIN_FUNC_SIZEOF is the last entry 24 * - that you add: 25 * 1. PLUGIN_CALL_... as callback-dispatcher 26 * 2. count and assignment in plugins_call_init() 27 * 28 */ 29 30 typedef enum { 31 PLUGIN_FUNC_HANDLE_URI_CLEAN, 32 PLUGIN_FUNC_HANDLE_URI_RAW, 33 PLUGIN_FUNC_HANDLE_REQUEST_ENV, 34 PLUGIN_FUNC_HANDLE_REQUEST_DONE, 35 PLUGIN_FUNC_HANDLE_CONNECTION_ACCEPT, 36 PLUGIN_FUNC_HANDLE_CONNECTION_SHUT_WR, 37 PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, 38 PLUGIN_FUNC_HANDLE_TRIGGER, 39 PLUGIN_FUNC_HANDLE_SIGHUP, 40 PLUGIN_FUNC_HANDLE_WAITPID, 41 /* PLUGIN_FUNC_HANDLE_SUBREQUEST, *//* max one handler_module per req */ 42 PLUGIN_FUNC_HANDLE_SUBREQUEST_START, 43 PLUGIN_FUNC_HANDLE_RESPONSE_START, 44 PLUGIN_FUNC_HANDLE_DOCROOT, 45 PLUGIN_FUNC_HANDLE_PHYSICAL, 46 PLUGIN_FUNC_CONNECTION_RESET, 47 /* PLUGIN_FUNC_INIT, *//* handled here in plugin.c */ 48 /* PLUGIN_FUNC_CLEANUP, *//* handled here in plugin.c */ 49 PLUGIN_FUNC_SET_DEFAULTS, 50 PLUGIN_FUNC_WORKER_INIT, 51 52 PLUGIN_FUNC_SIZEOF 53 } plugin_t; 54 55 static plugin *plugin_init(void) { 56 plugin *p; 57 58 p = calloc(1, sizeof(*p)); 59 force_assert(NULL != p); 60 61 return p; 62 } 63 64 static void plugin_free(plugin *p) { 65 if (NULL == p) return; /*(should not happen w/ current usage)*/ 66 #if !defined(LIGHTTPD_STATIC) 67 if (p->lib) { 68 #if defined(HAVE_VALGRIND_VALGRIND_H) 69 /*if (!RUNNING_ON_VALGRIND) */ 70 #endif 71 #if defined(__WIN32) 72 FreeLibrary(p->lib); 73 #else 74 dlclose(p->lib); 75 #endif 76 } 77 #endif 78 79 free(p); 80 } 81 82 static void plugins_register(server *srv, plugin *p) { 83 plugin **ps; 84 if (srv->plugins.used == srv->plugins.size) { 85 srv->plugins.size += 4; 86 srv->plugins.ptr = realloc(srv->plugins.ptr, srv->plugins.size * sizeof(*ps)); 87 force_assert(NULL != srv->plugins.ptr); 88 } 89 90 ps = srv->plugins.ptr; 91 ps[srv->plugins.used++] = p; 92 } 93 94 /** 95 * 96 * 97 * 98 */ 99 100 #if defined(LIGHTTPD_STATIC) 101 102 /* pre-declare functions, as there is no header for them */ 103 #define PLUGIN_INIT(x)\ 104 int x ## _plugin_init(plugin *p); 105 106 #include "plugin-static.h" 107 108 #undef PLUGIN_INIT 109 110 /* build NULL-terminated table of name + init-function */ 111 112 typedef struct { 113 const char* name; 114 int (*plugin_init)(plugin *p); 115 } plugin_load_functions; 116 117 static const plugin_load_functions load_functions[] = { 118 #define PLUGIN_INIT(x) \ 119 { #x, &x ## _plugin_init }, 120 121 #include "plugin-static.h" 122 123 { NULL, NULL } 124 #undef PLUGIN_INIT 125 }; 126 127 int plugins_load(server *srv) { 128 for (uint32_t i = 0; i < srv->srvconf.modules->used; ++i) { 129 data_string *ds = (data_string *)srv->srvconf.modules->data[i]; 130 char *module = ds->value.ptr; 131 132 uint32_t j; 133 for (j = 0; j < i; ++j) { 134 if (buffer_is_equal(&ds->value, &((data_string *) srv->srvconf.modules->data[j])->value)) { 135 log_error(srv->errh, __FILE__, __LINE__, 136 "Cannot load plugin %s " 137 "more than once, please fix your config (lighttpd may not accept such configs in future releases)", 138 ds->value.ptr); 139 continue; 140 } 141 } 142 143 for (j = 0; load_functions[j].name; ++j) { 144 if (0 == strcmp(load_functions[j].name, module)) { 145 plugin * const p = plugin_init(); 146 if ((*load_functions[j].plugin_init)(p)) { 147 log_error(srv->errh, __FILE__, __LINE__, "%s plugin init failed", module); 148 plugin_free(p); 149 return -1; 150 } 151 plugins_register(srv, p); 152 break; 153 } 154 } 155 if (!load_functions[j].name) { 156 log_error(srv->errh, __FILE__, __LINE__, "%s plugin not found", module); 157 return -1; 158 } 159 } 160 161 return 0; 162 } 163 #else /* defined(LIGHTTPD_STATIC) */ 164 int plugins_load(server *srv) { 165 buffer * const tb = srv->tmp_buf; 166 plugin *p; 167 int (*init)(plugin *pl); 168 size_t i, j; 169 170 for (i = 0; i < srv->srvconf.modules->used; i++) { 171 data_string *ds = (data_string *)srv->srvconf.modules->data[i]; 172 char *module = ds->value.ptr; 173 174 for (j = 0; j < i; j++) { 175 if (buffer_is_equal(&ds->value, &((data_string *) srv->srvconf.modules->data[j])->value)) { 176 log_error(srv->errh, __FILE__, __LINE__, 177 "Cannot load plugin %s " 178 "more than once, please fix your config (lighttpd may not accept such configs in future releases)", 179 ds->value.ptr); 180 continue; 181 } 182 } 183 184 buffer_copy_buffer(tb, srv->srvconf.modules_dir); 185 186 buffer_append_string_len(tb, CONST_STR_LEN("/")); 187 buffer_append_string(tb, module); 188 #if defined(__WIN32) || defined(__CYGWIN__) 189 buffer_append_string_len(tb, CONST_STR_LEN(".dll")); 190 #else 191 buffer_append_string_len(tb, CONST_STR_LEN(".so")); 192 #endif 193 194 p = plugin_init(); 195 #ifdef __WIN32 196 if (NULL == (p->lib = LoadLibrary(tb->ptr))) { 197 LPVOID lpMsgBuf; 198 FormatMessage( 199 FORMAT_MESSAGE_ALLOCATE_BUFFER | 200 FORMAT_MESSAGE_FROM_SYSTEM, 201 NULL, 202 GetLastError(), 203 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 204 (LPTSTR) &lpMsgBuf, 205 0, NULL); 206 207 log_error(srv->errh, __FILE__, __LINE__, 208 "LoadLibrary() failed %s %s", lpMsgBuf, tb->ptr); 209 210 plugin_free(p); 211 212 return -1; 213 214 } 215 #else 216 if (NULL == (p->lib = dlopen(tb->ptr, RTLD_NOW|RTLD_GLOBAL))) { 217 log_error(srv->errh, __FILE__, __LINE__, 218 "dlopen() failed for: %s %s", tb->ptr, dlerror()); 219 220 plugin_free(p); 221 222 return -1; 223 } 224 225 #endif 226 buffer_copy_string(tb, module); 227 buffer_append_string_len(tb, CONST_STR_LEN("_plugin_init")); 228 229 #ifdef __WIN32 230 init = GetProcAddress(p->lib, tb->ptr); 231 232 if (init == NULL) { 233 LPVOID lpMsgBuf; 234 FormatMessage( 235 FORMAT_MESSAGE_ALLOCATE_BUFFER | 236 FORMAT_MESSAGE_FROM_SYSTEM, 237 NULL, 238 GetLastError(), 239 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 240 (LPTSTR) &lpMsgBuf, 241 0, NULL); 242 243 log_error(srv->errh, __FILE__, __LINE__, 244 "getprocaddress failed: %s %s", tb->ptr, lpMsgBuf); 245 246 plugin_free(p); 247 return -1; 248 } 249 250 #else 251 #if 1 252 init = (int (*)(plugin *))(intptr_t)dlsym(p->lib, tb->ptr); 253 #else 254 *(void **)(&init) = dlsym(p->lib, tb->ptr); 255 #endif 256 if (NULL == init) { 257 const char *error = dlerror(); 258 if (error != NULL) { 259 log_error(srv->errh, __FILE__, __LINE__, "dlsym: %s", error); 260 } else { 261 log_error(srv->errh, __FILE__, __LINE__, "dlsym symbol not found: %s", tb->ptr); 262 } 263 264 plugin_free(p); 265 return -1; 266 } 267 268 #endif 269 if ((*init)(p)) { 270 log_error(srv->errh, __FILE__, __LINE__, "%s plugin init failed", module); 271 272 plugin_free(p); 273 return -1; 274 } 275 #if 0 276 log_error(srv->errh, __FILE__, __LINE__, "%s plugin loaded", module); 277 #endif 278 plugins_register(srv, p); 279 } 280 281 return 0; 282 } 283 #endif /* defined(LIGHTTPD_STATIC) */ 284 285 typedef struct { 286 handler_t(*fn)(); 287 plugin_data_base *data; 288 } plugin_fn_data; 289 290 __attribute_hot__ 291 static handler_t plugins_call_fn_req_data(request_st * const r, const int e) { 292 const void * const plugin_slots = r->con->plugin_slots; 293 const uint32_t offset = ((const uint16_t *)plugin_slots)[e]; 294 if (0 == offset) return HANDLER_GO_ON; 295 const plugin_fn_data *plfd = (const plugin_fn_data *) 296 (((uintptr_t)plugin_slots) + offset); 297 handler_t rc = HANDLER_GO_ON; 298 while (plfd->fn && (rc = plfd->fn(r, plfd->data)) == HANDLER_GO_ON) 299 ++plfd; 300 return rc; 301 } 302 303 __attribute_hot__ 304 static handler_t plugins_call_fn_con_data(connection * const con, const int e) { 305 const void * const plugin_slots = con->plugin_slots; 306 const uint32_t offset = ((const uint16_t *)plugin_slots)[e]; 307 if (0 == offset) return HANDLER_GO_ON; 308 const plugin_fn_data *plfd = (const plugin_fn_data *) 309 (((uintptr_t)plugin_slots) + offset); 310 handler_t rc = HANDLER_GO_ON; 311 while (plfd->fn && (rc = plfd->fn(con, plfd->data)) == HANDLER_GO_ON) 312 ++plfd; 313 return rc; 314 } 315 316 static handler_t plugins_call_fn_srv_data(server * const srv, const int e) { 317 const uint32_t offset = ((const uint16_t *)srv->plugin_slots)[e]; 318 if (0 == offset) return HANDLER_GO_ON; 319 const plugin_fn_data *plfd = (const plugin_fn_data *) 320 (((uintptr_t)srv->plugin_slots) + offset); 321 handler_t rc = HANDLER_GO_ON; 322 while (plfd->fn && (rc = plfd->fn(srv,plfd->data)) == HANDLER_GO_ON) 323 ++plfd; 324 return rc; 325 } 326 327 static void plugins_call_fn_srv_data_all(server * const srv, const int e) { 328 const uint32_t offset = ((const uint16_t *)srv->plugin_slots)[e]; 329 if (0 == offset) return; 330 const plugin_fn_data *plfd = (const plugin_fn_data *) 331 (((uintptr_t)srv->plugin_slots) + offset); 332 for (; plfd->fn; ++plfd) 333 plfd->fn(srv, plfd->data); 334 } 335 336 /** 337 * plugins that use 338 * 339 * - request_st *r 340 * - void *p_d (plugin_data *) 341 */ 342 343 #define PLUGIN_CALL_FN_REQ_DATA(x, y) \ 344 handler_t plugins_call_##y(request_st * const r) {\ 345 return plugins_call_fn_req_data(r, x); \ 346 } 347 348 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean) 349 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw) 350 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_REQUEST_ENV, handle_request_env) 351 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done) 352 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start) 353 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_RESPONSE_START, handle_response_start) 354 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot) 355 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical) 356 PLUGIN_CALL_FN_REQ_DATA(PLUGIN_FUNC_CONNECTION_RESET, handle_request_reset) 357 358 /** 359 * plugins that use 360 * 361 * - connection *con 362 * - void *p_d (plugin_data *) 363 */ 364 365 #define PLUGIN_CALL_FN_CON_DATA(x, y) \ 366 handler_t plugins_call_##y(connection *con) {\ 367 return plugins_call_fn_con_data(con, x); \ 368 } 369 370 PLUGIN_CALL_FN_CON_DATA(PLUGIN_FUNC_HANDLE_CONNECTION_ACCEPT, handle_connection_accept) 371 PLUGIN_CALL_FN_CON_DATA(PLUGIN_FUNC_HANDLE_CONNECTION_SHUT_WR, handle_connection_shut_wr) 372 PLUGIN_CALL_FN_CON_DATA(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close) 373 374 #undef PLUGIN_CALL_FN_SRV_CON_DATA 375 376 /** 377 * plugins that use 378 * 379 * - server *srv 380 * - void *p_d (plugin_data *) 381 */ 382 383 handler_t plugins_call_set_defaults(server *srv) { 384 return plugins_call_fn_srv_data(srv, PLUGIN_FUNC_SET_DEFAULTS); 385 } 386 387 handler_t plugins_call_worker_init(server *srv) { 388 return plugins_call_fn_srv_data(srv, PLUGIN_FUNC_WORKER_INIT); 389 } 390 391 void plugins_call_handle_trigger(server *srv) { 392 plugins_call_fn_srv_data_all(srv, PLUGIN_FUNC_HANDLE_TRIGGER); 393 } 394 395 void plugins_call_handle_sighup(server *srv) { 396 plugins_call_fn_srv_data_all(srv, PLUGIN_FUNC_HANDLE_SIGHUP); 397 } 398 399 handler_t plugins_call_handle_waitpid(server *srv, pid_t pid, int status) { 400 const uint32_t offset = 401 ((const uint16_t *)srv->plugin_slots)[PLUGIN_FUNC_HANDLE_WAITPID]; 402 if (0 == offset) return HANDLER_GO_ON; 403 const plugin_fn_data *plfd = (const plugin_fn_data *) 404 (((uintptr_t)srv->plugin_slots) + offset); 405 handler_t rc = HANDLER_GO_ON; 406 while (plfd->fn&&(rc=plfd->fn(srv,plfd->data,pid,status))==HANDLER_GO_ON) 407 ++plfd; 408 return rc; 409 } 410 411 static void plugins_call_cleanup(server * const srv) { 412 plugin ** const ps = srv->plugins.ptr; 413 for (uint32_t i = 0; i < srv->plugins.used; ++i) { 414 plugin *p = ps[i]; 415 if (NULL == p) continue; 416 if (NULL != p->data) { 417 plugin_data_base *pd = p->data; 418 if (p->cleanup) 419 p->cleanup(p->data); 420 free(pd->cvlist); 421 free(pd); 422 p->data = NULL; 423 } 424 } 425 } 426 427 /** 428 * 429 * - call init function of all plugins to init the plugin-internals 430 * - added each plugin that supports has callback to the corresponding slot 431 * 432 * - is only called once. 433 */ 434 435 __attribute_cold__ 436 static void plugins_call_init_slot(server *srv, handler_t(*fn)(), void *data, const uint32_t offset) { 437 if (fn) { 438 plugin_fn_data *plfd = (plugin_fn_data *) 439 (((uintptr_t)srv->plugin_slots) + offset); 440 while (plfd->fn) ++plfd; 441 plfd->fn = fn; 442 plfd->data = data; 443 } 444 } 445 446 handler_t plugins_call_init(server *srv) { 447 plugin ** const ps = srv->plugins.ptr; 448 uint16_t offsets[PLUGIN_FUNC_SIZEOF]; 449 memset(offsets, 0, sizeof(offsets)); 450 451 for (uint32_t i = 0; i < srv->plugins.used; ++i) { 452 /* check which calls are supported */ 453 454 plugin *p = ps[i]; 455 456 if (p->init) { 457 if (NULL == (p->data = p->init())) { 458 log_error(srv->errh, __FILE__, __LINE__, 459 "plugin-init failed for module %s", p->name); 460 return HANDLER_ERROR; 461 } 462 463 ((plugin_data_base *)(p->data))->self = p; 464 ((plugin_data_base *)(p->data))->id = i + 1; 465 466 if (p->version != LIGHTTPD_VERSION_ID) { 467 log_error(srv->errh, __FILE__, __LINE__, 468 "plugin-version doesn't match lighttpd-version for %s", p->name); 469 return HANDLER_ERROR; 470 } 471 } 472 473 if (p->priv_defaults && HANDLER_ERROR==p->priv_defaults(srv, p->data)) { 474 return HANDLER_ERROR; 475 } 476 477 if (p->handle_uri_clean) 478 ++offsets[PLUGIN_FUNC_HANDLE_URI_CLEAN]; 479 if (p->handle_uri_raw) 480 ++offsets[PLUGIN_FUNC_HANDLE_URI_RAW]; 481 if (p->handle_request_env) 482 ++offsets[PLUGIN_FUNC_HANDLE_REQUEST_ENV]; 483 if (p->handle_request_done) 484 ++offsets[PLUGIN_FUNC_HANDLE_REQUEST_DONE]; 485 if (p->handle_connection_accept) 486 ++offsets[PLUGIN_FUNC_HANDLE_CONNECTION_ACCEPT]; 487 if (p->handle_connection_shut_wr) 488 ++offsets[PLUGIN_FUNC_HANDLE_CONNECTION_SHUT_WR]; 489 if (p->handle_connection_close) 490 ++offsets[PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE]; 491 if (p->handle_trigger) 492 ++offsets[PLUGIN_FUNC_HANDLE_TRIGGER]; 493 if (p->handle_sighup) 494 ++offsets[PLUGIN_FUNC_HANDLE_SIGHUP]; 495 if (p->handle_waitpid) 496 ++offsets[PLUGIN_FUNC_HANDLE_WAITPID]; 497 if (p->handle_subrequest_start) 498 ++offsets[PLUGIN_FUNC_HANDLE_SUBREQUEST_START]; 499 if (p->handle_response_start) 500 ++offsets[PLUGIN_FUNC_HANDLE_RESPONSE_START]; 501 if (p->handle_docroot) 502 ++offsets[PLUGIN_FUNC_HANDLE_DOCROOT]; 503 if (p->handle_physical) 504 ++offsets[PLUGIN_FUNC_HANDLE_PHYSICAL]; 505 if (p->handle_request_reset) 506 ++offsets[PLUGIN_FUNC_CONNECTION_RESET]; 507 if (p->set_defaults) 508 ++offsets[PLUGIN_FUNC_SET_DEFAULTS]; 509 if (p->worker_init) 510 ++offsets[PLUGIN_FUNC_WORKER_INIT]; 511 } 512 513 uint32_t nslots = 514 (sizeof(offsets)+sizeof(plugin_fn_data)-1) / sizeof(plugin_fn_data); 515 for (uint32_t i = 0; i < PLUGIN_FUNC_SIZEOF; ++i) { 516 if (offsets[i]) { 517 uint32_t offset = nslots; 518 nslots += offsets[i]+1; /* +1 to mark end of each list */ 519 force_assert(offset * sizeof(plugin_fn_data) <= USHRT_MAX); 520 offsets[i] = (uint16_t)(offset * sizeof(plugin_fn_data)); 521 } 522 } 523 524 /* allocate and fill slots of two dimensional array */ 525 srv->plugin_slots = calloc(nslots, sizeof(plugin_fn_data)); 526 force_assert(NULL != srv->plugin_slots); 527 memcpy(srv->plugin_slots, offsets, sizeof(offsets)); 528 529 for (uint32_t i = 0; i < srv->plugins.used; ++i) { 530 plugin * const p = ps[i]; 531 532 plugins_call_init_slot(srv, p->handle_uri_clean, p->data, 533 offsets[PLUGIN_FUNC_HANDLE_URI_CLEAN]); 534 plugins_call_init_slot(srv, p->handle_uri_raw, p->data, 535 offsets[PLUGIN_FUNC_HANDLE_URI_RAW]); 536 plugins_call_init_slot(srv, p->handle_request_env, p->data, 537 offsets[PLUGIN_FUNC_HANDLE_REQUEST_ENV]); 538 plugins_call_init_slot(srv, p->handle_request_done, p->data, 539 offsets[PLUGIN_FUNC_HANDLE_REQUEST_DONE]); 540 plugins_call_init_slot(srv, p->handle_connection_accept, p->data, 541 offsets[PLUGIN_FUNC_HANDLE_CONNECTION_ACCEPT]); 542 plugins_call_init_slot(srv, p->handle_connection_shut_wr, p->data, 543 offsets[PLUGIN_FUNC_HANDLE_CONNECTION_SHUT_WR]); 544 plugins_call_init_slot(srv, p->handle_connection_close, p->data, 545 offsets[PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE]); 546 plugins_call_init_slot(srv, p->handle_trigger, p->data, 547 offsets[PLUGIN_FUNC_HANDLE_TRIGGER]); 548 plugins_call_init_slot(srv, p->handle_sighup, p->data, 549 offsets[PLUGIN_FUNC_HANDLE_SIGHUP]); 550 plugins_call_init_slot(srv, p->handle_waitpid, p->data, 551 offsets[PLUGIN_FUNC_HANDLE_WAITPID]); 552 plugins_call_init_slot(srv, p->handle_subrequest_start, p->data, 553 offsets[PLUGIN_FUNC_HANDLE_SUBREQUEST_START]); 554 plugins_call_init_slot(srv, p->handle_response_start, p->data, 555 offsets[PLUGIN_FUNC_HANDLE_RESPONSE_START]); 556 plugins_call_init_slot(srv, p->handle_docroot, p->data, 557 offsets[PLUGIN_FUNC_HANDLE_DOCROOT]); 558 plugins_call_init_slot(srv, p->handle_physical, p->data, 559 offsets[PLUGIN_FUNC_HANDLE_PHYSICAL]); 560 plugins_call_init_slot(srv, p->handle_request_reset, p->data, 561 offsets[PLUGIN_FUNC_CONNECTION_RESET]); 562 plugins_call_init_slot(srv, p->set_defaults, p->data, 563 offsets[PLUGIN_FUNC_SET_DEFAULTS]); 564 plugins_call_init_slot(srv, p->worker_init, p->data, 565 offsets[PLUGIN_FUNC_WORKER_INIT]); 566 } 567 568 return HANDLER_GO_ON; 569 } 570 571 void plugins_free(server *srv) { 572 if (srv->plugin_slots) { 573 plugins_call_cleanup(srv); 574 free(srv->plugin_slots); 575 srv->plugin_slots = NULL; 576 } 577 578 for (uint32_t i = 0; i < srv->plugins.used; ++i) { 579 plugin_free(((plugin **)srv->plugins.ptr)[i]); 580 } 581 free(srv->plugins.ptr); 582 srv->plugins.ptr = NULL; 583 srv->plugins.used = 0; 584 srv->plugins.size = 0; 585 array_free_data(&plugin_stats); 586 } 587