blob: b1d2e54df724502044d8b671c31d735ac6ddc02e [file] [log] [blame]
William Lallemand74c24fb2016-11-21 17:18:36 +01001/*
2 * Functions dedicated to statistics output and the stats socket
3 *
4 * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
14#include <ctype.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <pwd.h>
21#include <grp.h>
22
23#include <sys/socket.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26
27#include <common/cfgparse.h>
28#include <common/compat.h>
29#include <common/config.h>
30#include <common/debug.h>
31#include <common/memory.h>
32#include <common/mini-clist.h>
33#include <common/standard.h>
34#include <common/ticks.h>
35#include <common/time.h>
36#include <common/uri_auth.h>
37#include <common/version.h>
38#include <common/base64.h>
39
40#include <types/applet.h>
William Lallemand9ed62032016-11-21 17:49:11 +010041#include <types/cli.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010042#include <types/global.h>
43#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010044#include <types/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010045
46#include <proto/backend.h>
47#include <proto/channel.h>
48#include <proto/checks.h>
49#include <proto/compression.h>
William Lallemand9ed62032016-11-21 17:49:11 +010050#include <proto/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010051#include <proto/fd.h>
52#include <proto/freq_ctr.h>
53#include <proto/frontend.h>
54#include <proto/log.h>
55#include <proto/pattern.h>
56#include <proto/pipe.h>
57#include <proto/listener.h>
58#include <proto/map.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010059#include <proto/proto_uxst.h>
60#include <proto/proxy.h>
61#include <proto/sample.h>
62#include <proto/session.h>
63#include <proto/stream.h>
64#include <proto/server.h>
65#include <proto/raw_sock.h>
66#include <proto/stream_interface.h>
67#include <proto/task.h>
68
William Lallemand74c24fb2016-11-21 17:18:36 +010069static int stats_dump_env_to_buffer(struct stream_interface *si);
William Lallemand74c24fb2016-11-21 17:18:36 +010070
71static struct applet cli_applet;
72
73static const char stats_sock_usage_msg[] =
74 "Unknown command. Please enter one of the following commands only :\n"
75 " clear counters : clear max statistics counters (add 'all' for all counters)\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010076 " help : this message\n"
77 " prompt : toggle interactive mode with prompt\n"
78 " quit : disconnect\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010079 " show env [var] : dump environment variables known to the process\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010080 " set timeout : change a timeout setting\n"
81 " set maxconn : change a maxconn setting\n"
82 " set rate-limit : change a rate limiting value\n"
83 " disable : put a server or frontend in maintenance mode\n"
84 " enable : re-enable a server or frontend which is in maintenance mode\n"
85 " shutdown : kill a session or a frontend (eg:to release listening ports)\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010086 "";
87
88static const char stats_permission_denied_msg[] =
89 "Permission denied\n"
90 "";
91
92
93static char *dynamic_usage_msg = NULL;
94
95/* List head of cli keywords */
96static struct cli_kw_list cli_keywords = {
97 .list = LIST_HEAD_INIT(cli_keywords.list)
98};
99
100extern const char *stat_status_codes[];
101
102char *cli_gen_usage_msg()
103{
104 struct cli_kw_list *kw_list;
105 struct cli_kw *kw;
106 struct chunk *tmp = get_trash_chunk();
107 struct chunk out;
108
109 free(dynamic_usage_msg);
110 dynamic_usage_msg = NULL;
111
112 if (LIST_ISEMPTY(&cli_keywords.list))
113 return NULL;
114
115 chunk_reset(tmp);
116 chunk_strcat(tmp, stats_sock_usage_msg);
117 list_for_each_entry(kw_list, &cli_keywords.list, list) {
118 kw = &kw_list->kw[0];
119 while (kw->usage) {
120 chunk_appendf(tmp, " %s\n", kw->usage);
121 kw++;
122 }
123 }
124 chunk_init(&out, NULL, 0);
125 chunk_dup(&out, tmp);
126 dynamic_usage_msg = out.str;
127 return dynamic_usage_msg;
128}
129
130struct cli_kw* cli_find_kw(char **args)
131{
132 struct cli_kw_list *kw_list;
133 struct cli_kw *kw;/* current cli_kw */
134 char **tmp_args;
135 const char **tmp_str_kw;
136 int found = 0;
137
138 if (LIST_ISEMPTY(&cli_keywords.list))
139 return NULL;
140
141 list_for_each_entry(kw_list, &cli_keywords.list, list) {
142 kw = &kw_list->kw[0];
143 while (*kw->str_kw) {
144 tmp_args = args;
145 tmp_str_kw = kw->str_kw;
146 while (*tmp_str_kw) {
147 if (strcmp(*tmp_str_kw, *tmp_args) == 0) {
148 found = 1;
149 } else {
150 found = 0;
151 break;
152 }
153 tmp_args++;
154 tmp_str_kw++;
155 }
156 if (found)
157 return (kw);
158 kw++;
159 }
160 }
161 return NULL;
162}
163
164void cli_register_kw(struct cli_kw_list *kw_list)
165{
166 LIST_ADDQ(&cli_keywords.list, &kw_list->list);
167}
168
169
170/* allocate a new stats frontend named <name>, and return it
171 * (or NULL in case of lack of memory).
172 */
173static struct proxy *alloc_stats_fe(const char *name, const char *file, int line)
174{
175 struct proxy *fe;
176
177 fe = calloc(1, sizeof(*fe));
178 if (!fe)
179 return NULL;
180
181 init_new_proxy(fe);
182 fe->next = proxy;
183 proxy = fe;
184 fe->last_change = now.tv_sec;
185 fe->id = strdup("GLOBAL");
186 fe->cap = PR_CAP_FE;
187 fe->maxconn = 10; /* default to 10 concurrent connections */
188 fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */
189 fe->conf.file = strdup(file);
190 fe->conf.line = line;
191 fe->accept = frontend_accept;
192 fe->default_target = &cli_applet.obj_type;
193
194 /* the stats frontend is the only one able to assign ID #0 */
195 fe->conf.id.key = fe->uuid = 0;
196 eb32_insert(&used_proxy_id, &fe->conf.id);
197 return fe;
198}
199
200/* This function parses a "stats" statement in the "global" section. It returns
201 * -1 if there is any error, otherwise zero. If it returns -1, it will write an
202 * error message into the <err> buffer which will be preallocated. The trailing
203 * '\n' must not be written. The function must be called with <args> pointing to
204 * the first word after "stats".
205 */
206static int stats_parse_global(char **args, int section_type, struct proxy *curpx,
207 struct proxy *defpx, const char *file, int line,
208 char **err)
209{
210 struct bind_conf *bind_conf;
211 struct listener *l;
212
213 if (!strcmp(args[1], "socket")) {
214 int cur_arg;
215
216 if (*args[2] == 0) {
217 memprintf(err, "'%s %s' in global section expects an address or a path to a UNIX socket", args[0], args[1]);
218 return -1;
219 }
220
221 if (!global.stats_fe) {
222 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
223 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
224 return -1;
225 }
226 }
227
228 bind_conf = bind_conf_alloc(&global.stats_fe->conf.bind, file, line, args[2]);
229 bind_conf->level = ACCESS_LVL_OPER; /* default access level */
230
231 if (!str2listener(args[2], global.stats_fe, bind_conf, file, line, err)) {
232 memprintf(err, "parsing [%s:%d] : '%s %s' : %s\n",
233 file, line, args[0], args[1], err && *err ? *err : "error");
234 return -1;
235 }
236
237 cur_arg = 3;
238 while (*args[cur_arg]) {
239 static int bind_dumped;
240 struct bind_kw *kw;
241
242 kw = bind_find_kw(args[cur_arg]);
243 if (kw) {
244 if (!kw->parse) {
245 memprintf(err, "'%s %s' : '%s' option is not implemented in this version (check build options).",
246 args[0], args[1], args[cur_arg]);
247 return -1;
248 }
249
250 if (kw->parse(args, cur_arg, global.stats_fe, bind_conf, err) != 0) {
251 if (err && *err)
252 memprintf(err, "'%s %s' : '%s'", args[0], args[1], *err);
253 else
254 memprintf(err, "'%s %s' : error encountered while processing '%s'",
255 args[0], args[1], args[cur_arg]);
256 return -1;
257 }
258
259 cur_arg += 1 + kw->skip;
260 continue;
261 }
262
263 if (!bind_dumped) {
264 bind_dump_kws(err);
265 indent_msg(err, 4);
266 bind_dumped = 1;
267 }
268
269 memprintf(err, "'%s %s' : unknown keyword '%s'.%s%s",
270 args[0], args[1], args[cur_arg],
271 err && *err ? " Registered keywords :" : "", err && *err ? *err : "");
272 return -1;
273 }
274
275 list_for_each_entry(l, &bind_conf->listeners, by_bind) {
276 l->maxconn = global.stats_fe->maxconn;
277 l->backlog = global.stats_fe->backlog;
278 l->accept = session_accept_fd;
279 l->handler = process_stream;
280 l->default_target = global.stats_fe->default_target;
281 l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
282 l->nice = -64; /* we want to boost priority for local stats */
283 global.maxsock += l->maxconn;
284 }
285 }
286 else if (!strcmp(args[1], "timeout")) {
287 unsigned timeout;
288 const char *res = parse_time_err(args[2], &timeout, TIME_UNIT_MS);
289
290 if (res) {
291 memprintf(err, "'%s %s' : unexpected character '%c'", args[0], args[1], *res);
292 return -1;
293 }
294
295 if (!timeout) {
296 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
297 return -1;
298 }
299 if (!global.stats_fe) {
300 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
301 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
302 return -1;
303 }
304 }
305 global.stats_fe->timeout.client = MS_TO_TICKS(timeout);
306 }
307 else if (!strcmp(args[1], "maxconn")) {
308 int maxconn = atol(args[2]);
309
310 if (maxconn <= 0) {
311 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
312 return -1;
313 }
314
315 if (!global.stats_fe) {
316 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
317 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
318 return -1;
319 }
320 }
321 global.stats_fe->maxconn = maxconn;
322 }
323 else if (!strcmp(args[1], "bind-process")) { /* enable the socket only on some processes */
324 int cur_arg = 2;
325 unsigned long set = 0;
326
327 if (!global.stats_fe) {
328 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
329 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
330 return -1;
331 }
332 }
333
334 while (*args[cur_arg]) {
335 unsigned int low, high;
336
337 if (strcmp(args[cur_arg], "all") == 0) {
338 set = 0;
339 break;
340 }
341 else if (strcmp(args[cur_arg], "odd") == 0) {
342 set |= ~0UL/3UL; /* 0x555....555 */
343 }
344 else if (strcmp(args[cur_arg], "even") == 0) {
345 set |= (~0UL/3UL) << 1; /* 0xAAA...AAA */
346 }
347 else if (isdigit((int)*args[cur_arg])) {
348 char *dash = strchr(args[cur_arg], '-');
349
350 low = high = str2uic(args[cur_arg]);
351 if (dash)
352 high = str2uic(dash + 1);
353
354 if (high < low) {
355 unsigned int swap = low;
356 low = high;
357 high = swap;
358 }
359
360 if (low < 1 || high > LONGBITS) {
361 memprintf(err, "'%s %s' supports process numbers from 1 to %d.\n",
362 args[0], args[1], LONGBITS);
363 return -1;
364 }
365 while (low <= high)
366 set |= 1UL << (low++ - 1);
367 }
368 else {
369 memprintf(err,
370 "'%s %s' expects 'all', 'odd', 'even', or a list of process ranges with numbers from 1 to %d.\n",
371 args[0], args[1], LONGBITS);
372 return -1;
373 }
374 cur_arg++;
375 }
376 global.stats_fe->bind_proc = set;
377 }
378 else {
379 memprintf(err, "'%s' only supports 'socket', 'maxconn', 'bind-process' and 'timeout' (got '%s')", args[0], args[1]);
380 return -1;
381 }
382 return 0;
383}
384
Willy Tarreaude57a572016-11-23 17:01:39 +0100385/* Verifies that the CLI at least has a level at least as high as <level>
386 * (typically ACCESS_LVL_ADMIN). Returns 1 if OK, otherwise 0. In case of
387 * failure, an error message is prepared and the appctx's state is adjusted
388 * to print it so that a return 1 is enough to abort any processing.
389 */
390int cli_has_level(struct appctx *appctx, int level)
391{
392 struct stream_interface *si = appctx->owner;
393 struct stream *s = si_strm(si);
394
395 if (strm_li(s)->bind_conf->level < level) {
396 appctx->ctx.cli.msg = stats_permission_denied_msg;
397 appctx->st0 = STAT_CLI_PRINT;
398 return 0;
399 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100400 return 1;
401}
402
William Lallemand74c24fb2016-11-21 17:18:36 +0100403
404/* Expects to find a frontend named <arg> and returns it, otherwise displays various
405 * adequate error messages and returns NULL. This function also expects the stream
406 * level to be admin.
407 */
408static struct proxy *expect_frontend_admin(struct stream *s, struct stream_interface *si, const char *arg)
409{
410 struct appctx *appctx = __objt_appctx(si->end);
411 struct proxy *px;
412
413 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
414 appctx->ctx.cli.msg = stats_permission_denied_msg;
415 appctx->st0 = STAT_CLI_PRINT;
416 return NULL;
417 }
418
419 if (!*arg) {
420 appctx->ctx.cli.msg = "A frontend name is expected.\n";
421 appctx->st0 = STAT_CLI_PRINT;
422 return NULL;
423 }
424
425 px = proxy_fe_by_name(arg);
426 if (!px) {
427 appctx->ctx.cli.msg = "No such frontend.\n";
428 appctx->st0 = STAT_CLI_PRINT;
429 return NULL;
430 }
431 return px;
432}
433
434/* Expects to find a backend and a server in <arg> under the form <backend>/<server>,
435 * and returns the pointer to the server. Otherwise, display adequate error messages
436 * and returns NULL. This function also expects the stream level to be admin. Note:
437 * the <arg> is modified to remove the '/'.
438 */
William Lallemand222baf22016-11-19 02:00:33 +0100439struct server *expect_server_admin(struct stream *s, struct stream_interface *si, char *arg)
William Lallemand74c24fb2016-11-21 17:18:36 +0100440{
441 struct appctx *appctx = __objt_appctx(si->end);
442 struct proxy *px;
443 struct server *sv;
444 char *line;
445
446 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
447 appctx->ctx.cli.msg = stats_permission_denied_msg;
448 appctx->st0 = STAT_CLI_PRINT;
449 return NULL;
450 }
451
452 /* split "backend/server" and make <line> point to server */
453 for (line = arg; *line; line++)
454 if (*line == '/') {
455 *line++ = '\0';
456 break;
457 }
458
459 if (!*line || !*arg) {
460 appctx->ctx.cli.msg = "Require 'backend/server'.\n";
461 appctx->st0 = STAT_CLI_PRINT;
462 return NULL;
463 }
464
465 if (!get_backend_server(arg, line, &px, &sv)) {
466 appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n";
467 appctx->st0 = STAT_CLI_PRINT;
468 return NULL;
469 }
470
471 if (px->state == PR_STSTOPPED) {
472 appctx->ctx.cli.msg = "Proxy is disabled.\n";
473 appctx->st0 = STAT_CLI_PRINT;
474 return NULL;
475 }
476
477 return sv;
478}
479
William Lallemand74c24fb2016-11-21 17:18:36 +0100480/* Processes the stats interpreter on the statistics socket. This function is
481 * called from an applet running in a stream interface. The function returns 1
482 * if the request was understood, otherwise zero. It sets appctx->st0 to a value
483 * designating the function which will have to process the request, which can
484 * also be the print function to display the return message set into cli.msg.
485 */
486static int stats_sock_parse_request(struct stream_interface *si, char *line)
487{
488 struct stream *s = si_strm(si);
489 struct appctx *appctx = __objt_appctx(si->end);
490 char *args[MAX_STATS_ARGS + 1];
491 struct cli_kw *kw;
492 int arg;
493 int i, j;
494
495 while (isspace((unsigned char)*line))
496 line++;
497
498 arg = 0;
499 args[arg] = line;
500
501 while (*line && arg < MAX_STATS_ARGS) {
502 if (*line == '\\') {
503 line++;
504 if (*line == '\0')
505 break;
506 }
507 else if (isspace((unsigned char)*line)) {
508 *line++ = '\0';
509
510 while (isspace((unsigned char)*line))
511 line++;
512
513 args[++arg] = line;
514 continue;
515 }
516
517 line++;
518 }
519
520 while (++arg <= MAX_STATS_ARGS)
521 args[arg] = line;
522
523 /* remove \ */
524 arg = 0;
525 while (*args[arg] != '\0') {
526 j = 0;
527 for (i=0; args[arg][i] != '\0'; i++) {
528 if (args[arg][i] == '\\')
529 continue;
530 args[arg][j] = args[arg][i];
531 j++;
532 }
533 args[arg][j] = '\0';
534 arg++;
535 }
536
537 appctx->ctx.stats.scope_str = 0;
538 appctx->ctx.stats.scope_len = 0;
539 appctx->ctx.stats.flags = 0;
540 if ((kw = cli_find_kw(args))) {
541 if (kw->parse) {
542 if (kw->parse(args, appctx, kw->private) == 0 && kw->io_handler) {
543 appctx->st0 = STAT_CLI_O_CUSTOM;
544 appctx->io_handler = kw->io_handler;
545 appctx->io_release = kw->io_release;
546 }
547 }
548 } else if (strcmp(args[0], "show") == 0) {
William Lallemand933efcd2016-11-22 12:34:16 +0100549 if (strcmp(args[1], "env") == 0) {
William Lallemand74c24fb2016-11-21 17:18:36 +0100550 extern char **environ;
551
552 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
553 appctx->ctx.cli.msg = stats_permission_denied_msg;
554 appctx->st0 = STAT_CLI_PRINT;
555 return 1;
556 }
557 appctx->ctx.env.var = environ;
558 appctx->st2 = STAT_ST_INIT;
559 appctx->st0 = STAT_CLI_O_ENV; // stats_dump_env_to_buffer
560
561 if (*args[2]) {
562 int len = strlen(args[2]);
563
564 for (; *appctx->ctx.env.var; appctx->ctx.env.var++) {
565 if (strncmp(*appctx->ctx.env.var, args[2], len) == 0 &&
566 (*appctx->ctx.env.var)[len] == '=')
567 break;
568 }
569 if (!*appctx->ctx.env.var) {
570 appctx->ctx.cli.msg = "Variable not found\n";
571 appctx->st0 = STAT_CLI_PRINT;
572 return 1;
573 }
574 appctx->st2 = STAT_ST_END;
575 }
576 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100577 else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */
578 return 0;
579 }
580 }
581 else if (strcmp(args[0], "clear") == 0) {
582 if (strcmp(args[1], "counters") == 0) {
583 struct proxy *px;
584 struct server *sv;
585 struct listener *li;
586 int clrall = 0;
587
588 if (strcmp(args[2], "all") == 0)
589 clrall = 1;
590
591 /* check permissions */
592 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER ||
593 (clrall && strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN)) {
594 appctx->ctx.cli.msg = stats_permission_denied_msg;
595 appctx->st0 = STAT_CLI_PRINT;
596 return 1;
597 }
598
599 for (px = proxy; px; px = px->next) {
600 if (clrall) {
601 memset(&px->be_counters, 0, sizeof(px->be_counters));
602 memset(&px->fe_counters, 0, sizeof(px->fe_counters));
603 }
604 else {
605 px->be_counters.conn_max = 0;
606 px->be_counters.p.http.rps_max = 0;
607 px->be_counters.sps_max = 0;
608 px->be_counters.cps_max = 0;
609 px->be_counters.nbpend_max = 0;
610
611 px->fe_counters.conn_max = 0;
612 px->fe_counters.p.http.rps_max = 0;
613 px->fe_counters.sps_max = 0;
614 px->fe_counters.cps_max = 0;
615 px->fe_counters.nbpend_max = 0;
616 }
617
618 for (sv = px->srv; sv; sv = sv->next)
619 if (clrall)
620 memset(&sv->counters, 0, sizeof(sv->counters));
621 else {
622 sv->counters.cur_sess_max = 0;
623 sv->counters.nbpend_max = 0;
624 sv->counters.sps_max = 0;
625 }
626
627 list_for_each_entry(li, &px->conf.listeners, by_fe)
628 if (li->counters) {
629 if (clrall)
630 memset(li->counters, 0, sizeof(*li->counters));
631 else
632 li->counters->conn_max = 0;
633 }
634 }
635
636 global.cps_max = 0;
637 global.sps_max = 0;
638 return 1;
639 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100640 else {
641 /* unknown "clear" argument */
642 return 0;
643 }
644 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100645 else if (strcmp(args[0], "set") == 0) {
William Lallemand6b160942016-11-22 12:34:35 +0100646 if (strcmp(args[1], "timeout") == 0) {
William Lallemand74c24fb2016-11-21 17:18:36 +0100647 if (strcmp(args[2], "cli") == 0) {
648 unsigned timeout;
649 const char *res;
650
651 if (!*args[3]) {
652 appctx->ctx.cli.msg = "Expects an integer value.\n";
653 appctx->st0 = STAT_CLI_PRINT;
654 return 1;
655 }
656
657 res = parse_time_err(args[3], &timeout, TIME_UNIT_S);
658 if (res || timeout < 1) {
659 appctx->ctx.cli.msg = "Invalid timeout value.\n";
660 appctx->st0 = STAT_CLI_PRINT;
661 return 1;
662 }
663
664 s->req.rto = s->res.wto = 1 + MS_TO_TICKS(timeout*1000);
665 task_wakeup(s->task, TASK_WOKEN_MSG); // recompute timeouts
666 return 1;
667 }
668 else {
669 appctx->ctx.cli.msg = "'set timeout' only supports 'cli'.\n";
670 appctx->st0 = STAT_CLI_PRINT;
671 return 1;
672 }
673 }
674 else if (strcmp(args[1], "maxconn") == 0) {
675 if (strcmp(args[2], "frontend") == 0) {
676 struct proxy *px;
677 struct listener *l;
678 int v;
679
680 px = expect_frontend_admin(s, si, args[3]);
681 if (!px)
682 return 1;
683
684 if (!*args[4]) {
685 appctx->ctx.cli.msg = "Integer value expected.\n";
686 appctx->st0 = STAT_CLI_PRINT;
687 return 1;
688 }
689
690 v = atoi(args[4]);
691 if (v < 0) {
692 appctx->ctx.cli.msg = "Value out of range.\n";
693 appctx->st0 = STAT_CLI_PRINT;
694 return 1;
695 }
696
697 /* OK, the value is fine, so we assign it to the proxy and to all of
698 * its listeners. The blocked ones will be dequeued.
699 */
700 px->maxconn = v;
701 list_for_each_entry(l, &px->conf.listeners, by_fe) {
702 l->maxconn = v;
703 if (l->state == LI_FULL)
704 resume_listener(l);
705 }
706
707 if (px->maxconn > px->feconn && !LIST_ISEMPTY(&px->listener_queue))
708 dequeue_all_listeners(&px->listener_queue);
709
710 return 1;
711 }
712 else if (strcmp(args[2], "server") == 0) {
713 struct server *sv;
714 const char *warning;
715
716 sv = expect_server_admin(s, si, args[3]);
717 if (!sv)
718 return 1;
719
720 warning = server_parse_maxconn_change_request(sv, args[4]);
721 if (warning) {
722 appctx->ctx.cli.msg = warning;
723 appctx->st0 = STAT_CLI_PRINT;
724 }
725
726 return 1;
727 }
728 else if (strcmp(args[2], "global") == 0) {
729 int v;
730
731 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
732 appctx->ctx.cli.msg = stats_permission_denied_msg;
733 appctx->st0 = STAT_CLI_PRINT;
734 return 1;
735 }
736
737 if (!*args[3]) {
738 appctx->ctx.cli.msg = "Expects an integer value.\n";
739 appctx->st0 = STAT_CLI_PRINT;
740 return 1;
741 }
742
743 v = atoi(args[3]);
744 if (v > global.hardmaxconn) {
745 appctx->ctx.cli.msg = "Value out of range.\n";
746 appctx->st0 = STAT_CLI_PRINT;
747 return 1;
748 }
749
750 /* check for unlimited values */
751 if (v <= 0)
752 v = global.hardmaxconn;
753
754 global.maxconn = v;
755
756 /* Dequeues all of the listeners waiting for a resource */
757 if (!LIST_ISEMPTY(&global_listener_queue))
758 dequeue_all_listeners(&global_listener_queue);
759
760 return 1;
761 }
762 else {
763 appctx->ctx.cli.msg = "'set maxconn' only supports 'frontend', 'server', and 'global'.\n";
764 appctx->st0 = STAT_CLI_PRINT;
765 return 1;
766 }
767 }
768 else if (strcmp(args[1], "rate-limit") == 0) {
769 if (strcmp(args[2], "connections") == 0) {
770 if (strcmp(args[3], "global") == 0) {
771 int v;
772
773 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
774 appctx->ctx.cli.msg = stats_permission_denied_msg;
775 appctx->st0 = STAT_CLI_PRINT;
776 return 1;
777 }
778
779 if (!*args[4]) {
780 appctx->ctx.cli.msg = "Expects an integer value.\n";
781 appctx->st0 = STAT_CLI_PRINT;
782 return 1;
783 }
784
785 v = atoi(args[4]);
786 if (v < 0) {
787 appctx->ctx.cli.msg = "Value out of range.\n";
788 appctx->st0 = STAT_CLI_PRINT;
789 return 1;
790 }
791
792 global.cps_lim = v;
793
794 /* Dequeues all of the listeners waiting for a resource */
795 if (!LIST_ISEMPTY(&global_listener_queue))
796 dequeue_all_listeners(&global_listener_queue);
797
798 return 1;
799 }
800 else {
801 appctx->ctx.cli.msg = "'set rate-limit connections' only supports 'global'.\n";
802 appctx->st0 = STAT_CLI_PRINT;
803 return 1;
804 }
805 }
806 else if (strcmp(args[2], "sessions") == 0) {
807 if (strcmp(args[3], "global") == 0) {
808 int v;
809
810 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
811 appctx->ctx.cli.msg = stats_permission_denied_msg;
812 appctx->st0 = STAT_CLI_PRINT;
813 return 1;
814 }
815
816 if (!*args[4]) {
817 appctx->ctx.cli.msg = "Expects an integer value.\n";
818 appctx->st0 = STAT_CLI_PRINT;
819 return 1;
820 }
821
822 v = atoi(args[4]);
823 if (v < 0) {
824 appctx->ctx.cli.msg = "Value out of range.\n";
825 appctx->st0 = STAT_CLI_PRINT;
826 return 1;
827 }
828
829 global.sps_lim = v;
830
831 /* Dequeues all of the listeners waiting for a resource */
832 if (!LIST_ISEMPTY(&global_listener_queue))
833 dequeue_all_listeners(&global_listener_queue);
834
835 return 1;
836 }
837 else {
838 appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n";
839 appctx->st0 = STAT_CLI_PRINT;
840 return 1;
841 }
842 }
843#ifdef USE_OPENSSL
844 else if (strcmp(args[2], "ssl-sessions") == 0) {
845 if (strcmp(args[3], "global") == 0) {
846 int v;
847
848 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
849 appctx->ctx.cli.msg = stats_permission_denied_msg;
850 appctx->st0 = STAT_CLI_PRINT;
851 return 1;
852 }
853
854 if (!*args[4]) {
855 appctx->ctx.cli.msg = "Expects an integer value.\n";
856 appctx->st0 = STAT_CLI_PRINT;
857 return 1;
858 }
859
860 v = atoi(args[4]);
861 if (v < 0) {
862 appctx->ctx.cli.msg = "Value out of range.\n";
863 appctx->st0 = STAT_CLI_PRINT;
864 return 1;
865 }
866
867 global.ssl_lim = v;
868
869 /* Dequeues all of the listeners waiting for a resource */
870 if (!LIST_ISEMPTY(&global_listener_queue))
871 dequeue_all_listeners(&global_listener_queue);
872
873 return 1;
874 }
875 else {
876 appctx->ctx.cli.msg = "'set rate-limit ssl-sessions' only supports 'global'.\n";
877 appctx->st0 = STAT_CLI_PRINT;
878 return 1;
879 }
880 }
881#endif
882 else if (strcmp(args[2], "http-compression") == 0) {
883 if (strcmp(args[3], "global") == 0) {
884 int v;
885
886 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
887 appctx->ctx.cli.msg = stats_permission_denied_msg;
888 appctx->st0 = STAT_CLI_PRINT;
889 return 1;
890 }
891
892 if (!*args[4]) {
893 appctx->ctx.cli.msg = "Expects a maximum input byte rate in kB/s.\n";
894 appctx->st0 = STAT_CLI_PRINT;
895 return 1;
896 }
897
898 v = atoi(args[4]);
899 global.comp_rate_lim = v * 1024; /* Kilo to bytes. */
900 }
901 else {
902 appctx->ctx.cli.msg = "'set rate-limit http-compression' only supports 'global'.\n";
903 appctx->st0 = STAT_CLI_PRINT;
904 return 1;
905 }
906 }
907 else {
908 appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n";
909 appctx->st0 = STAT_CLI_PRINT;
910 return 1;
911 }
William Lallemand32af2032016-10-29 18:09:35 +0200912 } else { /* unknown "set" parameter */
William Lallemand74c24fb2016-11-21 17:18:36 +0100913 return 0;
914 }
915 }
916 else if (strcmp(args[0], "enable") == 0) {
917 if (strcmp(args[1], "agent") == 0) {
918 struct server *sv;
919
920 sv = expect_server_admin(s, si, args[2]);
921 if (!sv)
922 return 1;
923
924 if (!(sv->agent.state & CHK_ST_CONFIGURED)) {
925 appctx->ctx.cli.msg = "Agent was not configured on this server, cannot enable.\n";
926 appctx->st0 = STAT_CLI_PRINT;
927 return 1;
928 }
929
930 sv->agent.state |= CHK_ST_ENABLED;
931 return 1;
932 }
933 else if (strcmp(args[1], "health") == 0) {
934 struct server *sv;
935
936 sv = expect_server_admin(s, si, args[2]);
937 if (!sv)
938 return 1;
939
940 if (!(sv->check.state & CHK_ST_CONFIGURED)) {
941 appctx->ctx.cli.msg = "Health checks are not configured on this server, cannot enable.\n";
942 appctx->st0 = STAT_CLI_PRINT;
943 return 1;
944 }
945
946 sv->check.state |= CHK_ST_ENABLED;
947 return 1;
948 }
949 else if (strcmp(args[1], "server") == 0) {
950 struct server *sv;
951
952 sv = expect_server_admin(s, si, args[2]);
953 if (!sv)
954 return 1;
955
956 srv_adm_set_ready(sv);
957 return 1;
958 }
959 else if (strcmp(args[1], "frontend") == 0) {
960 struct proxy *px;
961
962 px = expect_frontend_admin(s, si, args[2]);
963 if (!px)
964 return 1;
965
966 if (px->state == PR_STSTOPPED) {
967 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n";
968 appctx->st0 = STAT_CLI_PRINT;
969 return 1;
970 }
971
972 if (px->state != PR_STPAUSED) {
973 appctx->ctx.cli.msg = "Frontend is already enabled.\n";
974 appctx->st0 = STAT_CLI_PRINT;
975 return 1;
976 }
977
978 if (!resume_proxy(px)) {
979 appctx->ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n";
980 appctx->st0 = STAT_CLI_PRINT;
981 return 1;
982 }
983 return 1;
984 }
985 else { /* unknown "enable" parameter */
986 appctx->ctx.cli.msg = "'enable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
987 appctx->st0 = STAT_CLI_PRINT;
988 return 1;
989 }
990 }
991 else if (strcmp(args[0], "disable") == 0) {
992 if (strcmp(args[1], "agent") == 0) {
993 struct server *sv;
994
995 sv = expect_server_admin(s, si, args[2]);
996 if (!sv)
997 return 1;
998
999 sv->agent.state &= ~CHK_ST_ENABLED;
1000 return 1;
1001 }
1002 else if (strcmp(args[1], "health") == 0) {
1003 struct server *sv;
1004
1005 sv = expect_server_admin(s, si, args[2]);
1006 if (!sv)
1007 return 1;
1008
1009 sv->check.state &= ~CHK_ST_ENABLED;
1010 return 1;
1011 }
1012 else if (strcmp(args[1], "server") == 0) {
1013 struct server *sv;
1014
1015 sv = expect_server_admin(s, si, args[2]);
1016 if (!sv)
1017 return 1;
1018
1019 srv_adm_set_maint(sv);
1020 return 1;
1021 }
1022 else if (strcmp(args[1], "frontend") == 0) {
1023 struct proxy *px;
1024
1025 px = expect_frontend_admin(s, si, args[2]);
1026 if (!px)
1027 return 1;
1028
1029 if (px->state == PR_STSTOPPED) {
1030 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n";
1031 appctx->st0 = STAT_CLI_PRINT;
1032 return 1;
1033 }
1034
1035 if (px->state == PR_STPAUSED) {
1036 appctx->ctx.cli.msg = "Frontend is already disabled.\n";
1037 appctx->st0 = STAT_CLI_PRINT;
1038 return 1;
1039 }
1040
1041 if (!pause_proxy(px)) {
1042 appctx->ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n";
1043 appctx->st0 = STAT_CLI_PRINT;
1044 return 1;
1045 }
1046 return 1;
1047 }
1048 else { /* unknown "disable" parameter */
1049 appctx->ctx.cli.msg = "'disable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
1050 appctx->st0 = STAT_CLI_PRINT;
1051 return 1;
1052 }
1053 }
1054 else if (strcmp(args[0], "shutdown") == 0) {
1055 if (strcmp(args[1], "frontend") == 0) {
1056 struct proxy *px;
1057
1058 px = expect_frontend_admin(s, si, args[2]);
1059 if (!px)
1060 return 1;
1061
1062 if (px->state == PR_STSTOPPED) {
1063 appctx->ctx.cli.msg = "Frontend was already shut down.\n";
1064 appctx->st0 = STAT_CLI_PRINT;
1065 return 1;
1066 }
1067
1068 Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1069 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1070 send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1071 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1072 stop_proxy(px);
1073 return 1;
1074 }
1075 else if (strcmp(args[1], "session") == 0) {
1076 struct stream *sess, *ptr;
1077
1078 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1079 appctx->ctx.cli.msg = stats_permission_denied_msg;
1080 appctx->st0 = STAT_CLI_PRINT;
1081 return 1;
1082 }
1083
1084 if (!*args[2]) {
1085 appctx->ctx.cli.msg = "Session pointer expected (use 'show sess').\n";
1086 appctx->st0 = STAT_CLI_PRINT;
1087 return 1;
1088 }
1089
1090 ptr = (void *)strtoul(args[2], NULL, 0);
1091
1092 /* first, look for the requested stream in the stream table */
1093 list_for_each_entry(sess, &streams, list) {
1094 if (sess == ptr)
1095 break;
1096 }
1097
1098 /* do we have the stream ? */
1099 if (sess != ptr) {
1100 appctx->ctx.cli.msg = "No such session (use 'show sess').\n";
1101 appctx->st0 = STAT_CLI_PRINT;
1102 return 1;
1103 }
1104
1105 stream_shutdown(sess, SF_ERR_KILLED);
1106 return 1;
1107 }
1108 else if (strcmp(args[1], "sessions") == 0) {
1109 if (strcmp(args[2], "server") == 0) {
1110 struct server *sv;
1111 struct stream *sess, *sess_bck;
1112
1113 sv = expect_server_admin(s, si, args[3]);
1114 if (!sv)
1115 return 1;
1116
1117 /* kill all the stream that are on this server */
1118 list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
1119 if (sess->srv_conn == sv)
1120 stream_shutdown(sess, SF_ERR_KILLED);
1121
1122 return 1;
1123 }
1124 else {
1125 appctx->ctx.cli.msg = "'shutdown sessions' only supports 'server'.\n";
1126 appctx->st0 = STAT_CLI_PRINT;
1127 return 1;
1128 }
1129 }
1130 else { /* unknown "disable" parameter */
1131 appctx->ctx.cli.msg = "'shutdown' only supports 'frontend', 'session' and 'sessions'.\n";
1132 appctx->st0 = STAT_CLI_PRINT;
1133 return 1;
1134 }
1135 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001136 else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */
1137 return 0;
1138 }
1139 return 1;
1140}
1141
1142/* This I/O handler runs as an applet embedded in a stream interface. It is
1143 * used to processes I/O from/to the stats unix socket. The system relies on a
1144 * state machine handling requests and various responses. We read a request,
1145 * then we process it and send the response, and we possibly display a prompt.
1146 * Then we can read again. The state is stored in appctx->st0 and is one of the
1147 * STAT_CLI_* constants. appctx->st1 is used to indicate whether prompt is enabled
1148 * or not.
1149 */
1150static void cli_io_handler(struct appctx *appctx)
1151{
1152 struct stream_interface *si = appctx->owner;
1153 struct channel *req = si_oc(si);
1154 struct channel *res = si_ic(si);
1155 int reql;
1156 int len;
1157
1158 if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
1159 goto out;
1160
1161 while (1) {
1162 if (appctx->st0 == STAT_CLI_INIT) {
1163 /* Stats output not initialized yet */
1164 memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats));
1165 appctx->st0 = STAT_CLI_GETREQ;
1166 }
1167 else if (appctx->st0 == STAT_CLI_END) {
1168 /* Let's close for real now. We just close the request
1169 * side, the conditions below will complete if needed.
1170 */
1171 si_shutw(si);
1172 break;
1173 }
1174 else if (appctx->st0 == STAT_CLI_GETREQ) {
1175 /* ensure we have some output room left in the event we
1176 * would want to return some info right after parsing.
1177 */
1178 if (buffer_almost_full(si_ib(si))) {
1179 si_applet_cant_put(si);
1180 break;
1181 }
1182
1183 reql = bo_getline(si_oc(si), trash.str, trash.size);
1184 if (reql <= 0) { /* closed or EOL not found */
1185 if (reql == 0)
1186 break;
1187 appctx->st0 = STAT_CLI_END;
1188 continue;
1189 }
1190
1191 /* seek for a possible unescaped semi-colon. If we find
1192 * one, we replace it with an LF and skip only this part.
1193 */
1194 for (len = 0; len < reql; len++) {
1195 if (trash.str[len] == '\\') {
1196 len++;
1197 continue;
1198 }
1199 if (trash.str[len] == ';') {
1200 trash.str[len] = '\n';
1201 reql = len + 1;
1202 break;
1203 }
1204 }
1205
1206 /* now it is time to check that we have a full line,
1207 * remove the trailing \n and possibly \r, then cut the
1208 * line.
1209 */
1210 len = reql - 1;
1211 if (trash.str[len] != '\n') {
1212 appctx->st0 = STAT_CLI_END;
1213 continue;
1214 }
1215
1216 if (len && trash.str[len-1] == '\r')
1217 len--;
1218
1219 trash.str[len] = '\0';
1220
1221 appctx->st0 = STAT_CLI_PROMPT;
1222 if (len) {
1223 if (strcmp(trash.str, "quit") == 0) {
1224 appctx->st0 = STAT_CLI_END;
1225 continue;
1226 }
1227 else if (strcmp(trash.str, "prompt") == 0)
1228 appctx->st1 = !appctx->st1;
1229 else if (strcmp(trash.str, "help") == 0 ||
1230 !stats_sock_parse_request(si, trash.str)) {
1231 cli_gen_usage_msg();
1232 if (dynamic_usage_msg)
1233 appctx->ctx.cli.msg = dynamic_usage_msg;
1234 else
1235 appctx->ctx.cli.msg = stats_sock_usage_msg;
1236 appctx->st0 = STAT_CLI_PRINT;
1237 }
1238 /* NB: stats_sock_parse_request() may have put
1239 * another STAT_CLI_O_* into appctx->st0.
1240 */
1241 }
1242 else if (!appctx->st1) {
1243 /* if prompt is disabled, print help on empty lines,
1244 * so that the user at least knows how to enable
1245 * prompt and find help.
1246 */
1247 cli_gen_usage_msg();
1248 if (dynamic_usage_msg)
1249 appctx->ctx.cli.msg = dynamic_usage_msg;
1250 else
1251 appctx->ctx.cli.msg = stats_sock_usage_msg;
1252 appctx->st0 = STAT_CLI_PRINT;
1253 }
1254
1255 /* re-adjust req buffer */
1256 bo_skip(si_oc(si), reql);
1257 req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */
1258 }
1259 else { /* output functions */
1260 switch (appctx->st0) {
1261 case STAT_CLI_PROMPT:
1262 break;
1263 case STAT_CLI_PRINT:
1264 if (bi_putstr(si_ic(si), appctx->ctx.cli.msg) != -1)
1265 appctx->st0 = STAT_CLI_PROMPT;
1266 else
1267 si_applet_cant_put(si);
1268 break;
1269 case STAT_CLI_PRINT_FREE:
1270 if (bi_putstr(si_ic(si), appctx->ctx.cli.err) != -1) {
1271 free(appctx->ctx.cli.err);
1272 appctx->st0 = STAT_CLI_PROMPT;
1273 }
1274 else
1275 si_applet_cant_put(si);
1276 break;
William Lallemand74c24fb2016-11-21 17:18:36 +01001277 case STAT_CLI_O_ENV: /* environment dump */
1278 if (stats_dump_env_to_buffer(si))
1279 appctx->st0 = STAT_CLI_PROMPT;
1280 break;
1281 case STAT_CLI_O_CUSTOM: /* use custom pointer */
1282 if (appctx->io_handler)
1283 if (appctx->io_handler(appctx)) {
1284 appctx->st0 = STAT_CLI_PROMPT;
1285 if (appctx->io_release) {
1286 appctx->io_release(appctx);
1287 appctx->io_release = NULL;
1288 }
1289 }
1290 break;
1291 default: /* abnormal state */
1292 si->flags |= SI_FL_ERR;
1293 break;
1294 }
1295
1296 /* The post-command prompt is either LF alone or LF + '> ' in interactive mode */
1297 if (appctx->st0 == STAT_CLI_PROMPT) {
1298 if (bi_putstr(si_ic(si), appctx->st1 ? "\n> " : "\n") != -1)
1299 appctx->st0 = STAT_CLI_GETREQ;
1300 else
1301 si_applet_cant_put(si);
1302 }
1303
1304 /* If the output functions are still there, it means they require more room. */
1305 if (appctx->st0 >= STAT_CLI_OUTPUT)
1306 break;
1307
1308 /* Now we close the output if one of the writers did so,
1309 * or if we're not in interactive mode and the request
1310 * buffer is empty. This still allows pipelined requests
1311 * to be sent in non-interactive mode.
1312 */
1313 if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) {
1314 appctx->st0 = STAT_CLI_END;
1315 continue;
1316 }
1317
1318 /* switch state back to GETREQ to read next requests */
1319 appctx->st0 = STAT_CLI_GETREQ;
1320 }
1321 }
1322
1323 if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST)) {
1324 DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
1325 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1326 /* Other side has closed, let's abort if we have no more processing to do
1327 * and nothing more to consume. This is comparable to a broken pipe, so
1328 * we forward the close to the request side so that it flows upstream to
1329 * the client.
1330 */
1331 si_shutw(si);
1332 }
1333
1334 if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && (appctx->st0 < STAT_CLI_OUTPUT)) {
1335 DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n",
1336 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1337 /* We have no more processing to do, and nothing more to send, and
1338 * the client side has closed. So we'll forward this state downstream
1339 * on the response buffer.
1340 */
1341 si_shutr(si);
1342 res->flags |= CF_READ_NULL;
1343 }
1344
1345 out:
1346 DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rqh=%d, rqs=%d, rh=%d, rs=%d\n",
1347 __FUNCTION__, __LINE__,
1348 si->state, req->flags, res->flags, req->buf->i, req->buf->o, res->buf->i, res->buf->o);
1349}
1350
William Lallemand74c24fb2016-11-21 17:18:36 +01001351/* This is called when the stream interface is closed. For instance, upon an
1352 * external abort, we won't call the i/o handler anymore so we may need to
1353 * remove back references to the stream currently being dumped.
1354 */
1355static void cli_release_handler(struct appctx *appctx)
1356{
1357 if (appctx->io_release) {
1358 appctx->io_release(appctx);
1359 appctx->io_release = NULL;
1360 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001361 else if (appctx->st0 == STAT_CLI_PRINT_FREE) {
1362 free(appctx->ctx.cli.err);
1363 appctx->ctx.cli.err = NULL;
1364 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001365}
1366
1367/* This function dumps all environmnent variables to the buffer. It returns 0
1368 * if the output buffer is full and it needs to be called again, otherwise
1369 * non-zero. Dumps only one entry if st2 == STAT_ST_END.
1370 */
1371static int stats_dump_env_to_buffer(struct stream_interface *si)
1372{
1373 struct appctx *appctx = __objt_appctx(si->end);
1374
1375 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1376 return 1;
1377
1378 chunk_reset(&trash);
1379
1380 /* we have two inner loops here, one for the proxy, the other one for
1381 * the buffer.
1382 */
1383 while (*appctx->ctx.env.var) {
1384 chunk_printf(&trash, "%s\n", *appctx->ctx.env.var);
1385
1386 if (bi_putchk(si_ic(si), &trash) == -1) {
1387 si_applet_cant_put(si);
1388 return 0;
1389 }
1390 if (appctx->st2 == STAT_ST_END)
1391 break;
1392 appctx->ctx.env.var++;
1393 }
1394
1395 /* dump complete */
1396 return 1;
1397}
1398
1399/* parse the "level" argument on the bind lines */
1400static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
1401{
1402 if (!*args[cur_arg + 1]) {
1403 memprintf(err, "'%s' : missing level", args[cur_arg]);
1404 return ERR_ALERT | ERR_FATAL;
1405 }
1406
1407 if (!strcmp(args[cur_arg+1], "user"))
1408 conf->level = ACCESS_LVL_USER;
1409 else if (!strcmp(args[cur_arg+1], "operator"))
1410 conf->level = ACCESS_LVL_OPER;
1411 else if (!strcmp(args[cur_arg+1], "admin"))
1412 conf->level = ACCESS_LVL_ADMIN;
1413 else {
1414 memprintf(err, "'%s' only supports 'user', 'operator', and 'admin' (got '%s')",
1415 args[cur_arg], args[cur_arg+1]);
1416 return ERR_ALERT | ERR_FATAL;
1417 }
1418
1419 return 0;
1420}
1421
1422static struct applet cli_applet = {
1423 .obj_type = OBJ_TYPE_APPLET,
1424 .name = "<CLI>", /* used for logging */
1425 .fct = cli_io_handler,
1426 .release = cli_release_handler,
1427};
1428
1429static struct cfg_kw_list cfg_kws = {ILH, {
1430 { CFG_GLOBAL, "stats", stats_parse_global },
1431 { 0, NULL, NULL },
1432}};
1433
1434static struct bind_kw_list bind_kws = { "STAT", { }, {
1435 { "level", bind_parse_level, 1 }, /* set the unix socket admin level */
1436 { NULL, NULL, 0 },
1437}};
1438
1439__attribute__((constructor))
1440static void __dumpstats_module_init(void)
1441{
1442 cfg_register_keywords(&cfg_kws);
1443 bind_register_keywords(&bind_kws);
1444}
1445
1446/*
1447 * Local variables:
1448 * c-indent-level: 8
1449 * c-basic-offset: 8
1450 * End:
1451 */