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