[core] fix crash if invalid config file (fixes #2798)
[lighttpd.git] / src / mod_status.c
blob38e5bdd95a33729e67d024d85003e9fcceda480a
1 #include "first.h"
3 #include "server.h"
4 #include "connections.h"
5 #include "response.h"
6 #include "connections.h"
7 #include "log.h"
9 #include "plugin.h"
11 #include "inet_ntop_cache.h"
13 #include <sys/types.h>
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <errno.h>
20 #include <time.h>
21 #include <stdio.h>
23 typedef struct {
24 buffer *config_url;
25 buffer *status_url;
26 buffer *statistics_url;
28 int sort;
29 } plugin_config;
31 typedef struct {
32 PLUGIN_DATA;
34 double traffic_out;
35 double requests;
37 double mod_5s_traffic_out[5];
38 double mod_5s_requests[5];
39 size_t mod_5s_ndx;
41 double rel_traffic_out;
42 double rel_requests;
44 double abs_traffic_out;
45 double abs_requests;
47 double bytes_written;
49 buffer *module_list;
51 plugin_config **config_storage;
53 plugin_config conf;
54 } plugin_data;
56 INIT_FUNC(mod_status_init) {
57 plugin_data *p;
58 size_t i;
60 p = calloc(1, sizeof(*p));
62 p->traffic_out = p->requests = 0;
63 p->rel_traffic_out = p->rel_requests = 0;
64 p->abs_traffic_out = p->abs_requests = 0;
65 p->bytes_written = 0;
66 p->module_list = buffer_init();
68 for (i = 0; i < 5; i++) {
69 p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
72 return p;
75 FREE_FUNC(mod_status_free) {
76 plugin_data *p = p_d;
78 UNUSED(srv);
80 if (!p) return HANDLER_GO_ON;
82 buffer_free(p->module_list);
84 if (p->config_storage) {
85 size_t i;
86 for (i = 0; i < srv->config_context->used; i++) {
87 plugin_config *s = p->config_storage[i];
88 if (NULL == s) continue;
90 buffer_free(s->status_url);
91 buffer_free(s->statistics_url);
92 buffer_free(s->config_url);
94 free(s);
96 free(p->config_storage);
100 free(p);
102 return HANDLER_GO_ON;
105 SETDEFAULTS_FUNC(mod_status_set_defaults) {
106 plugin_data *p = p_d;
107 size_t i;
109 config_values_t cv[] = {
110 { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
111 { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
112 { "status.enable-sort", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
113 { "status.statistics-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
114 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
117 if (!p) return HANDLER_ERROR;
119 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
121 for (i = 0; i < srv->config_context->used; i++) {
122 data_config const* config = (data_config const*)srv->config_context->data[i];
123 plugin_config *s;
125 s = calloc(1, sizeof(plugin_config));
126 s->config_url = buffer_init();
127 s->status_url = buffer_init();
128 s->sort = 1;
129 s->statistics_url = buffer_init();
131 cv[0].destination = s->status_url;
132 cv[1].destination = s->config_url;
133 cv[2].destination = &(s->sort);
134 cv[3].destination = s->statistics_url;
136 p->config_storage[i] = s;
138 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
139 return HANDLER_ERROR;
143 return HANDLER_GO_ON;
148 static int mod_status_row_append(buffer *b, const char *key, const char *value) {
149 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
150 buffer_append_string_len(b, CONST_STR_LEN(" <td><b>"));
151 buffer_append_string(b, key);
152 buffer_append_string_len(b, CONST_STR_LEN("</b></td>\n"));
153 buffer_append_string_len(b, CONST_STR_LEN(" <td>"));
154 buffer_append_string(b, value);
155 buffer_append_string_len(b, CONST_STR_LEN("</td>\n"));
156 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
158 return 0;
161 static int mod_status_header_append(buffer *b, const char *key) {
162 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
163 buffer_append_string_len(b, CONST_STR_LEN(" <th colspan=\"2\">"));
164 buffer_append_string(b, key);
165 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
166 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
168 return 0;
171 static int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) {
172 plugin_data *p = p_d;
174 if (p->conf.sort) {
175 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"));
176 buffer_append_string(b, key);
177 buffer_append_string_len(b, CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"));
178 } else {
179 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\">"));
180 buffer_append_string(b, key);
181 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
184 return 0;
187 static int mod_status_get_multiplier(double *avg, char *multiplier, int size) {
188 *multiplier = ' ';
190 if (*avg > size) { *avg /= size; *multiplier = 'k'; }
191 if (*avg > size) { *avg /= size; *multiplier = 'M'; }
192 if (*avg > size) { *avg /= size; *multiplier = 'G'; }
193 if (*avg > size) { *avg /= size; *multiplier = 'T'; }
194 if (*avg > size) { *avg /= size; *multiplier = 'P'; }
195 if (*avg > size) { *avg /= size; *multiplier = 'E'; }
196 if (*avg > size) { *avg /= size; *multiplier = 'Z'; }
197 if (*avg > size) { *avg /= size; *multiplier = 'Y'; }
199 return 0;
202 static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) {
203 plugin_data *p = p_d;
204 buffer *b = buffer_init();
205 size_t j;
206 double avg;
207 char multiplier = '\0';
208 char buf[32];
209 time_t ts;
211 int days, hours, mins, seconds;
213 /*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
214 int cstates[CON_STATE_CLOSE+3];
215 memset(cstates, 0, sizeof(cstates));
217 buffer_copy_string_len(b, CONST_STR_LEN(
218 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
219 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
220 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
221 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
222 " <head>\n"
223 " <title>Status</title>\n"
225 " <style type=\"text/css\">\n"
226 " table.status { border: black solid thin; }\n"
227 " td { white-space: nowrap; }\n"
228 " td.int { background-color: #f0f0f0; text-align: right }\n"
229 " td.string { background-color: #f0f0f0; text-align: left }\n"
230 " th.status { background-color: black; color: white; font-weight: bold; }\n"
231 " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
232 " span.sortarrow { color: white; text-decoration: none; }\n"
233 " </style>\n"));
235 if (!buffer_string_is_empty(con->uri.query) && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("refresh="))) {
236 /* Note: Refresh is an historical, but non-standard HTTP header
237 * References (meta http-equiv="refresh" use is deprecated):
238 * https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
239 * https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
240 * https://www.w3.org/QA/Tips/reback
242 const long refresh = strtol(con->uri.query->ptr+sizeof("refresh=")-1, NULL, 10);
243 if (refresh > 0) {
244 buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
245 buffer_append_int(b, refresh < 604800 ? refresh : 604800);
246 buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
250 if (p->conf.sort) {
251 buffer_append_string_len(b, CONST_STR_LEN(
252 "<script type=\"text/javascript\">\n"
253 "// <!--\n"
254 "var sort_column;\n"
255 "var prev_span = null;\n"
257 "function get_inner_text(el) {\n"
258 " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
259 " return el;\n"
260 " if(el.innerText)\n"
261 " return el.innerText;\n"
262 " else {\n"
263 " var str = \"\";\n"
264 " var cs = el.childNodes;\n"
265 " var l = cs.length;\n"
266 " for (i=0;i<l;i++) {\n"
267 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
268 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
269 " }\n"
270 " }\n"
271 " return str;\n"
272 "}\n"
274 "function sortfn(a,b) {\n"
275 " var at = get_inner_text(a.cells[sort_column]);\n"
276 " var bt = get_inner_text(b.cells[sort_column]);\n"
277 " if (a.cells[sort_column].className == 'int') {\n"
278 " return parseInt(at)-parseInt(bt);\n"
279 " } else {\n"
280 " aa = at.toLowerCase();\n"
281 " bb = bt.toLowerCase();\n"
282 " if (aa==bb) return 0;\n"
283 " else if (aa<bb) return -1;\n"
284 " else return 1;\n"
285 " }\n"
286 "}\n"
288 "function resort(lnk) {\n"
289 " var span = lnk.childNodes[1];\n"
290 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
291 " var rows = new Array();\n"
292 " for (j=1;j<table.rows.length;j++)\n"
293 " rows[j-1] = table.rows[j];\n"
294 " sort_column = lnk.parentNode.cellIndex;\n"
295 " rows.sort(sortfn);\n"
297 " if (prev_span != null) prev_span.innerHTML = '';\n"
298 " if (span.getAttribute('sortdir')=='down') {\n"
299 " span.innerHTML = '&uarr;';\n"
300 " span.setAttribute('sortdir','up');\n"
301 " rows.reverse();\n"
302 " } else {\n"
303 " span.innerHTML = '&darr;';\n"
304 " span.setAttribute('sortdir','down');\n"
305 " }\n"
306 " for (i=0;i<rows.length;i++)\n"
307 " table.tBodies[0].appendChild(rows[i]);\n"
308 " prev_span = span;\n"
309 "}\n"
310 "// -->\n"
311 "</script>\n"));
314 buffer_append_string_len(b, CONST_STR_LEN(
315 " </head>\n"
316 " <body>\n"));
320 /* connection listing */
321 buffer_append_string_len(b, CONST_STR_LEN("<h1>Server-Status ("));
322 buffer_append_string_buffer(b, con->conf.server_tag);
323 buffer_append_string_len(b, CONST_STR_LEN(")</h1>"));
325 buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">"));
326 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Hostname</td><td class=\"string\">"));
327 buffer_append_string_buffer(b, con->uri.authority);
328 buffer_append_string_len(b, CONST_STR_LEN(" ("));
329 buffer_append_string_buffer(b, con->server_name);
330 buffer_append_string_len(b, CONST_STR_LEN(")</td></tr>\n"));
331 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Uptime</td><td class=\"string\">"));
333 ts = srv->cur_ts - srv->startup_ts;
335 days = ts / (60 * 60 * 24);
336 ts %= (60 * 60 * 24);
338 hours = ts / (60 * 60);
339 ts %= (60 * 60);
341 mins = ts / (60);
342 ts %= (60);
344 seconds = ts;
346 if (days) {
347 buffer_append_int(b, days);
348 buffer_append_string_len(b, CONST_STR_LEN(" days "));
351 if (hours) {
352 buffer_append_int(b, hours);
353 buffer_append_string_len(b, CONST_STR_LEN(" hours "));
356 if (mins) {
357 buffer_append_int(b, mins);
358 buffer_append_string_len(b, CONST_STR_LEN(" min "));
361 buffer_append_int(b, seconds);
362 buffer_append_string_len(b, CONST_STR_LEN(" s"));
364 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
365 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Started at</td><td class=\"string\">"));
367 ts = srv->startup_ts;
369 strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts));
370 buffer_append_string(b, buf);
371 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
374 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"));
376 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
377 avg = p->abs_requests;
379 mod_status_get_multiplier(&avg, &multiplier, 1000);
381 buffer_append_int(b, avg);
382 buffer_append_string_len(b, CONST_STR_LEN(" "));
383 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
384 buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n"));
386 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
387 avg = p->abs_traffic_out;
389 mod_status_get_multiplier(&avg, &multiplier, 1024);
391 snprintf(buf, sizeof(buf), "%.2f", avg);
392 buffer_append_string(b, buf);
393 buffer_append_string_len(b, CONST_STR_LEN(" "));
394 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
395 buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n"));
399 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (since start)</th></tr>\n"));
401 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
402 avg = p->abs_requests / (srv->cur_ts - srv->startup_ts);
404 mod_status_get_multiplier(&avg, &multiplier, 1000);
406 buffer_append_int(b, avg);
407 buffer_append_string_len(b, CONST_STR_LEN(" "));
408 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
409 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
411 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
412 avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts);
414 mod_status_get_multiplier(&avg, &multiplier, 1024);
416 snprintf(buf, sizeof(buf), "%.2f", avg);
417 buffer_append_string(b, buf);
418 buffer_append_string_len(b, CONST_STR_LEN(" "));
419 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
420 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
424 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"));
425 for (j = 0, avg = 0; j < 5; j++) {
426 avg += p->mod_5s_requests[j];
429 avg /= 5;
431 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
433 mod_status_get_multiplier(&avg, &multiplier, 1000);
435 buffer_append_int(b, avg);
436 buffer_append_string_len(b, CONST_STR_LEN(" "));
437 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
439 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
441 for (j = 0, avg = 0; j < 5; j++) {
442 avg += p->mod_5s_traffic_out[j];
445 avg /= 5;
447 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
449 mod_status_get_multiplier(&avg, &multiplier, 1024);
451 snprintf(buf, sizeof(buf), "%.2f", avg);
452 buffer_append_string(b, buf);
453 buffer_append_string_len(b, CONST_STR_LEN(" "));
454 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
455 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
457 buffer_append_string_len(b, CONST_STR_LEN("</table>\n"));
459 buffer_append_string_len(b, CONST_STR_LEN("<hr />\n<pre>\n"));
461 buffer_append_string_len(b, CONST_STR_LEN("<b>"));
462 buffer_append_int(b, srv->conns->used);
463 buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n"));
465 for (j = 0; j < srv->conns->used; j++) {
466 connection *c = srv->conns->ptr[j];
467 const char *state;
469 if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
470 state = "k";
471 ++cstates[CON_STATE_CLOSE+2];
472 } else {
473 state = connection_get_short_state(c->state);
474 ++cstates[(c->state <= CON_STATE_CLOSE ? c->state : CON_STATE_CLOSE+1)];
477 buffer_append_string_len(b, state, 1);
479 if (((j + 1) % 50) == 0) {
480 buffer_append_string_len(b, CONST_STR_LEN("\n"));
483 buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n"));
484 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
485 buffer_append_int(b, cstates[CON_STATE_CLOSE+2]);
486 buffer_append_string_len(b, CONST_STR_LEN("<td>&nbsp;&nbsp;k = keep-alive</td></tr>\n"));
487 for (j = 0; j < CON_STATE_CLOSE+2; ++j) {
488 /*(skip "unknown" state if there are none; there should not be any unknown)*/
489 if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue;
490 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
491 buffer_append_int(b, cstates[j]);
492 buffer_append_string_len(b, CONST_STR_LEN("</td><td>&nbsp;&nbsp;"));
493 buffer_append_string_len(b, connection_get_short_state(j), 1);
494 buffer_append_string_len(b, CONST_STR_LEN(" = "));
495 buffer_append_string(b, connection_get_state(j));
496 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
498 buffer_append_string_len(b, CONST_STR_LEN("</table>"));
500 buffer_append_string_len(b, CONST_STR_LEN("\n</pre><hr />\n<h2>Connections</h2>\n"));
502 buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">\n"));
503 buffer_append_string_len(b, CONST_STR_LEN("<tr>"));
504 mod_status_header_append_sort(b, p_d, "Client IP");
505 mod_status_header_append_sort(b, p_d, "Read");
506 mod_status_header_append_sort(b, p_d, "Written");
507 mod_status_header_append_sort(b, p_d, "State");
508 mod_status_header_append_sort(b, p_d, "Time");
509 mod_status_header_append_sort(b, p_d, "Host");
510 mod_status_header_append_sort(b, p_d, "URI");
511 mod_status_header_append_sort(b, p_d, "File");
512 buffer_append_string_len(b, CONST_STR_LEN("</tr>\n"));
514 for (j = 0; j < srv->conns->used; j++) {
515 connection *c = srv->conns->ptr[j];
517 buffer_append_string_len(b, CONST_STR_LEN("<tr><td class=\"string\">"));
519 buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr)));
521 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
523 if (c->request.content_length) {
524 buffer_append_int(b, c->request_content_queue->bytes_in);
525 buffer_append_string_len(b, CONST_STR_LEN("/"));
526 buffer_append_int(b, c->request.content_length);
527 } else {
528 buffer_append_string_len(b, CONST_STR_LEN("0/0"));
531 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
533 buffer_append_int(b, c->write_queue->bytes_out);
534 buffer_append_string_len(b, CONST_STR_LEN("/"));
535 buffer_append_int(b, c->write_queue->bytes_out + chunkqueue_length(c->write_queue));
537 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
539 if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
540 buffer_append_string_len(b, CONST_STR_LEN("keep-alive"));
541 } else {
542 buffer_append_string(b, connection_get_state(c->state));
545 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
547 buffer_append_int(b, srv->cur_ts - c->request_start);
549 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
551 if (buffer_string_is_empty(c->server_name)) {
552 buffer_append_string_buffer(b, c->uri.authority);
554 else {
555 buffer_append_string_buffer(b, c->server_name);
558 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
560 if (!buffer_string_is_empty(c->uri.path)) {
561 buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML);
564 if (!buffer_string_is_empty(c->uri.query)) {
565 buffer_append_string_len(b, CONST_STR_LEN("?"));
566 buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.query), ENCODING_HTML);
569 if (!buffer_string_is_empty(c->request.orig_uri)) {
570 buffer_append_string_len(b, CONST_STR_LEN(" ("));
571 buffer_append_string_encoded(b, CONST_BUF_LEN(c->request.orig_uri), ENCODING_HTML);
572 buffer_append_string_len(b, CONST_STR_LEN(")"));
574 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
576 buffer_append_string_buffer(b, c->physical.path);
578 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
582 buffer_append_string_len(b, CONST_STR_LEN(
583 "</table>\n"));
586 buffer_append_string_len(b, CONST_STR_LEN(
587 " </body>\n"
588 "</html>\n"
591 chunkqueue_append_buffer(con->write_queue, b);
592 buffer_free(b);
594 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
596 return 0;
600 static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) {
601 plugin_data *p = p_d;
602 buffer *b = buffer_init();
603 double avg;
604 time_t ts;
605 char buf[32];
606 unsigned int k;
607 unsigned int l;
609 /* output total number of requests */
610 buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: "));
611 avg = p->abs_requests;
612 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
613 buffer_append_string(b, buf);
614 buffer_append_string_len(b, CONST_STR_LEN("\n"));
616 /* output total traffic out in kbytes */
617 buffer_append_string_len(b, CONST_STR_LEN("Total kBytes: "));
618 avg = p->abs_traffic_out / 1024;
619 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
620 buffer_append_string(b, buf);
621 buffer_append_string_len(b, CONST_STR_LEN("\n"));
623 /* output uptime */
624 buffer_append_string_len(b, CONST_STR_LEN("Uptime: "));
625 ts = srv->cur_ts - srv->startup_ts;
626 buffer_append_int(b, ts);
627 buffer_append_string_len(b, CONST_STR_LEN("\n"));
629 /* output busy servers */
630 buffer_append_string_len(b, CONST_STR_LEN("BusyServers: "));
631 buffer_append_int(b, srv->conns->used);
632 buffer_append_string_len(b, CONST_STR_LEN("\n"));
634 buffer_append_string_len(b, CONST_STR_LEN("IdleServers: "));
635 buffer_append_int(b, srv->conns->size - srv->conns->used);
636 buffer_append_string_len(b, CONST_STR_LEN("\n"));
638 /* output scoreboard */
639 buffer_append_string_len(b, CONST_STR_LEN("Scoreboard: "));
640 for (k = 0; k < srv->conns->used; k++) {
641 connection *c = srv->conns->ptr[k];
642 const char *state =
643 (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri))
644 ? "k"
645 : connection_get_short_state(c->state);
646 buffer_append_string_len(b, state, 1);
648 for (l = 0; l < srv->conns->size - srv->conns->used; l++) {
649 buffer_append_string_len(b, CONST_STR_LEN("_"));
651 buffer_append_string_len(b, CONST_STR_LEN("\n"));
653 chunkqueue_append_buffer(con->write_queue, b);
654 buffer_free(b);
656 /* set text/plain output */
657 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
659 return 0;
663 static handler_t mod_status_handle_server_status_json(server *srv, connection *con, void *p_d) {
664 plugin_data *p = p_d;
665 buffer *b = buffer_init();
666 double avg;
667 time_t ts;
668 char buf[32];
669 size_t j;
670 unsigned int jsonp = 0;
672 if (buffer_string_length(con->uri.query) >= sizeof("jsonp=")-1
673 && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("jsonp="))) {
674 /* not a full parse of query string for multiple parameters,
675 * not URL-decoding param and not XML-encoding (XSS protection),
676 * so simply ensure that json function name isalnum() or '_' */
677 const char *f = con->uri.query->ptr + sizeof("jsonp=")-1;
678 int len = 0;
679 while (light_isalnum(f[len]) || f[len] == '_') ++len;
680 if (0 != len && light_isalpha(f[0]) && f[len] == '\0') {
681 buffer_append_string_len(b, f, len);
682 buffer_append_string_len(b, CONST_STR_LEN("("));
683 jsonp = 1;
687 /* output total number of requests */
688 buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": "));
689 avg = p->abs_requests;
690 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
691 buffer_append_string(b, buf);
692 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
694 /* output total traffic out in kbytes */
695 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficTotal\": "));
696 avg = p->abs_traffic_out / 1024;
697 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
698 buffer_append_string(b, buf);
699 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
701 /* output uptime */
702 buffer_append_string_len(b, CONST_STR_LEN("\t\"Uptime\": "));
703 ts = srv->cur_ts - srv->startup_ts;
704 buffer_append_int(b, ts);
705 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
707 /* output busy servers */
708 buffer_append_string_len(b, CONST_STR_LEN("\t\"BusyServers\": "));
709 buffer_append_int(b, srv->conns->used);
710 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
712 buffer_append_string_len(b, CONST_STR_LEN("\t\"IdleServers\": "));
713 buffer_append_int(b, srv->conns->size - srv->conns->used);
714 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
716 for (j = 0, avg = 0; j < 5; j++) {
717 avg += p->mod_5s_requests[j];
720 avg /= 5;
722 buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":"));
723 buffer_append_int(b, avg);
724 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
726 for (j = 0, avg = 0; j < 5; j++) {
727 avg += p->mod_5s_traffic_out[j];
730 avg /= 5;
732 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":"));
733 buffer_append_int(b, avg / 1024); /* kbps */
734 buffer_append_string_len(b, CONST_STR_LEN("\n}"));
736 if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");"));
738 chunkqueue_append_buffer(con->write_queue, b);
739 buffer_free(b);
741 /* set text/plain output */
742 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript"));
744 return 0;
748 static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) {
749 buffer *b;
750 size_t i;
751 array *st = srv->status;
752 UNUSED(p_d);
754 if (0 == st->used) {
755 /* we have nothing to send */
756 con->http_status = 204;
757 con->file_finished = 1;
759 return HANDLER_FINISHED;
762 b = buffer_init();
763 for (i = 0; i < st->used; i++) {
764 size_t ndx = st->sorted[i];
766 buffer_append_string_buffer(b, st->data[ndx]->key);
767 buffer_append_string_len(b, CONST_STR_LEN(": "));
768 buffer_append_int(b, ((data_integer *)(st->data[ndx]))->value);
769 buffer_append_string_len(b, CONST_STR_LEN("\n"));
772 chunkqueue_append_buffer(con->write_queue, b);
773 buffer_free(b);
775 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
777 con->http_status = 200;
778 con->file_finished = 1;
780 return HANDLER_FINISHED;
784 static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) {
786 if (buffer_is_equal_string(con->uri.query, CONST_STR_LEN("auto"))) {
787 mod_status_handle_server_status_text(srv, con, p_d);
788 } else if (buffer_string_length(con->uri.query) >= sizeof("json")-1
789 && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("json"))) {
790 mod_status_handle_server_status_json(srv, con, p_d);
791 } else {
792 mod_status_handle_server_status_html(srv, con, p_d);
795 con->http_status = 200;
796 con->file_finished = 1;
798 return HANDLER_FINISHED;
802 static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) {
803 plugin_data *p = p_d;
804 buffer *b = buffer_init();
805 buffer *m = p->module_list;
806 size_t i;
808 struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
810 /* - epoll is most reliable
811 * - select works everywhere
813 #ifdef USE_LINUX_EPOLL
814 { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
815 #endif
816 #ifdef USE_POLL
817 { FDEVENT_HANDLER_POLL, "poll" },
818 #endif
819 #ifdef USE_SELECT
820 { FDEVENT_HANDLER_SELECT, "select" },
821 #endif
822 #ifdef USE_LIBEV
823 { FDEVENT_HANDLER_LIBEV, "libev" },
824 #endif
825 #ifdef USE_SOLARIS_DEVPOLL
826 { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
827 #endif
828 #ifdef USE_SOLARIS_PORT
829 { FDEVENT_HANDLER_SOLARIS_PORT, "solaris-eventports" },
830 #endif
831 #ifdef USE_FREEBSD_KQUEUE
832 { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
833 #endif
834 { FDEVENT_HANDLER_UNSET, NULL }
837 buffer_copy_string_len(b, CONST_STR_LEN(
838 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
839 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
840 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
841 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
842 " <head>\n"
843 " <title>Status</title>\n"
844 " </head>\n"
845 " <body>\n"
846 " <h1>"));
847 buffer_append_string_buffer(b, con->conf.server_tag);
848 buffer_append_string_len(b, CONST_STR_LEN(
849 "</h1>\n"
850 " <table summary=\"status\" border=\"1\">\n"));
852 mod_status_header_append(b, "Server-Features");
853 #ifdef HAVE_PCRE_H
854 mod_status_row_append(b, "RegEx Conditionals", "enabled");
855 #else
856 mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing");
857 #endif
858 mod_status_header_append(b, "Network Engine");
860 for (i = 0; event_handlers[i].name; i++) {
861 if (event_handlers[i].et == srv->event_handler) {
862 mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name);
863 break;
867 mod_status_header_append(b, "Config-File-Settings");
869 for (i = 0; i < srv->plugins.used; i++) {
870 plugin **ps = srv->plugins.ptr;
872 plugin *pl = ps[i];
874 if (i == 0) {
875 buffer_copy_buffer(m, pl->name);
876 } else {
877 buffer_append_string_len(m, CONST_STR_LEN("<br />"));
878 buffer_append_string_buffer(m, pl->name);
882 mod_status_row_append(b, "Loaded Modules", m->ptr);
884 buffer_append_string_len(b, CONST_STR_LEN(" </table>\n"));
886 buffer_append_string_len(b, CONST_STR_LEN(
887 " </body>\n"
888 "</html>\n"
891 chunkqueue_append_buffer(con->write_queue, b);
892 buffer_free(b);
894 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
896 con->http_status = 200;
897 con->file_finished = 1;
899 return HANDLER_FINISHED;
902 #define PATCH(x) \
903 p->conf.x = s->x;
904 static int mod_status_patch_connection(server *srv, connection *con, plugin_data *p) {
905 size_t i, j;
906 plugin_config *s = p->config_storage[0];
908 PATCH(status_url);
909 PATCH(config_url);
910 PATCH(sort);
911 PATCH(statistics_url);
913 /* skip the first, the global context */
914 for (i = 1; i < srv->config_context->used; i++) {
915 data_config *dc = (data_config *)srv->config_context->data[i];
916 s = p->config_storage[i];
918 /* condition didn't match */
919 if (!config_check_cond(srv, con, dc)) continue;
921 /* merge config */
922 for (j = 0; j < dc->value->used; j++) {
923 data_unset *du = dc->value->data[j];
925 if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
926 PATCH(status_url);
927 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
928 PATCH(config_url);
929 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) {
930 PATCH(sort);
931 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) {
932 PATCH(statistics_url);
937 return 0;
940 static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
941 plugin_data *p = p_d;
943 if (con->mode != DIRECT) return HANDLER_GO_ON;
945 mod_status_patch_connection(srv, con, p);
947 if (!buffer_string_is_empty(p->conf.status_url) &&
948 buffer_is_equal(p->conf.status_url, con->uri.path)) {
949 return mod_status_handle_server_status(srv, con, p_d);
950 } else if (!buffer_string_is_empty(p->conf.config_url) &&
951 buffer_is_equal(p->conf.config_url, con->uri.path)) {
952 return mod_status_handle_server_config(srv, con, p_d);
953 } else if (!buffer_string_is_empty(p->conf.statistics_url) &&
954 buffer_is_equal(p->conf.statistics_url, con->uri.path)) {
955 return mod_status_handle_server_statistics(srv, con, p_d);
958 return HANDLER_GO_ON;
961 TRIGGER_FUNC(mod_status_trigger) {
962 plugin_data *p = p_d;
963 size_t i;
965 /* check all connections */
966 for (i = 0; i < srv->conns->used; i++) {
967 connection *c = srv->conns->ptr[i];
969 p->bytes_written += c->bytes_written_cur_second;
972 /* a sliding average */
973 p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
974 p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
976 p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
978 p->abs_traffic_out += p->bytes_written;
979 p->rel_traffic_out += p->bytes_written;
981 p->bytes_written = 0;
983 /* reset storage - second */
984 p->traffic_out = 0;
985 p->requests = 0;
987 return HANDLER_GO_ON;
990 REQUESTDONE_FUNC(mod_status_account) {
991 plugin_data *p = p_d;
993 UNUSED(srv);
995 p->requests++;
996 p->rel_requests++;
997 p->abs_requests++;
999 p->bytes_written += con->bytes_written_cur_second;
1001 return HANDLER_GO_ON;
1004 int mod_status_plugin_init(plugin *p);
1005 int mod_status_plugin_init(plugin *p) {
1006 p->version = LIGHTTPD_VERSION_ID;
1007 p->name = buffer_init_string("status");
1009 p->init = mod_status_init;
1010 p->cleanup = mod_status_free;
1011 p->set_defaults= mod_status_set_defaults;
1013 p->handle_uri_clean = mod_status_handler;
1014 p->handle_trigger = mod_status_trigger;
1015 p->handle_request_done = mod_status_account;
1017 p->data = NULL;
1019 return 0;