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