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