blob: 65e2d89678defed639c1fce5b74a3088220388b5 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id$ */
2/*
3 * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#include <pjlib-util/cli_imp.h>
21#include <pjlib-util/cli_console.h>
22#include <pj/assert.h>
23#include <pj/errno.h>
24#include <pj/log.h>
25#include <pj/os.h>
26#include <pj/pool.h>
27#include <pj/string.h>
28#include <pjlib-util/errno.h>
29
30/**
31 * This specify the state of output character parsing.
32 */
33typedef enum out_parse_state
34{
35 OP_NORMAL,
36 OP_TYPE,
37 OP_SHORTCUT,
38 OP_CHOICE
39} out_parse_state;
40
41struct cli_console_fe
42{
43 pj_cli_front_end base;
44 pj_pool_t *pool;
45 pj_cli_sess *sess;
46 pj_thread_t *input_thread;
47 pj_bool_t thread_quit;
48 pj_sem_t *thread_sem;
49 pj_cli_console_cfg cfg;
50
51 struct async_input_t
52 {
53 char *buf;
54 unsigned maxlen;
55 pj_sem_t *sem;
56 } input;
57};
58
59static void console_write_log(pj_cli_front_end *fe, int level,
60 const char *data, pj_size_t len)
61{
62 struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
63
64 if (cfe->sess->log_level > level)
65 printf("%.*s", (int)len, data);
66}
67
68static void console_quit(pj_cli_front_end *fe, pj_cli_sess *req)
69{
70 struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
71
72 PJ_UNUSED_ARG(req);
73
74 pj_assert(cfe);
75 if (cfe->input_thread) {
76 cfe->thread_quit = PJ_TRUE;
77 pj_sem_post(cfe->input.sem);
78 pj_sem_post(cfe->thread_sem);
79 }
80}
81
82static void console_destroy(pj_cli_front_end *fe)
83{
84 struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
85
86 pj_assert(cfe);
87 console_quit(fe, NULL);
88
89 if (cfe->input_thread)
90 pj_thread_join(cfe->input_thread);
91
92 if (cfe->input_thread) {
93 pj_thread_destroy(cfe->input_thread);
94 cfe->input_thread = NULL;
95 }
96
97 pj_sem_destroy(cfe->thread_sem);
98 pj_sem_destroy(cfe->input.sem);
99 pj_pool_release(cfe->pool);
100}
101
102PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param)
103{
104 pj_assert(param);
105
106 param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL;
107 param->prompt_str.slen = 0;
108 param->quit_command.slen = 0;
109}
110
111PJ_DEF(pj_status_t) pj_cli_console_create(pj_cli_t *cli,
112 const pj_cli_console_cfg *param,
113 pj_cli_sess **p_sess,
114 pj_cli_front_end **p_fe)
115{
116 pj_cli_sess *sess;
117 struct cli_console_fe *fe;
118 pj_cli_console_cfg cfg;
119 pj_pool_t *pool;
120 pj_status_t status;
121
122 PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL);
123
124 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe",
125 PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
126 NULL);
127 if (!pool)
128 return PJ_ENOMEM;
129
130 sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess);
131 fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe);
132
133 if (!param) {
134 pj_cli_console_cfg_default(&cfg);
135 param = &cfg;
136 }
137 sess->fe = &fe->base;
138 sess->log_level = param->log_level;
139 sess->op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op);
140 fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
141 fe->base.cli = cli;
142 fe->base.type = PJ_CLI_CONSOLE_FRONT_END;
143 fe->base.op->on_write_log = &console_write_log;
144 fe->base.op->on_quit = &console_quit;
145 fe->base.op->on_destroy = &console_destroy;
146 fe->pool = pool;
147 fe->sess = sess;
148 status = pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem);
149 if (status != PJ_SUCCESS)
150 return status;
151
152 status = pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem);
153 if (status != PJ_SUCCESS)
154 return status;
155
156 pj_cli_register_front_end(cli, &fe->base);
157 if (param->prompt_str.slen == 0) {
158 pj_str_t prompt_sign = pj_str(">>> ");
159 fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, prompt_sign.slen+1);
160 pj_strcpy(&fe->cfg.prompt_str, &prompt_sign);
161 } else {
162 fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool,
163 param->prompt_str.slen+1);
164 pj_strcpy(&fe->cfg.prompt_str, &param->prompt_str);
165 }
166 fe->cfg.prompt_str.ptr[fe->cfg.prompt_str.slen] = 0;
167
168 if (param->quit_command.slen)
169 pj_strdup(fe->pool, &fe->cfg.quit_command, &param->quit_command);
170
171 *p_sess = sess;
172 if (p_fe)
173 *p_fe = &fe->base;
174
175 return PJ_SUCCESS;
176}
177
178static void send_prompt_str(pj_cli_sess *sess)
179{
180 pj_str_t send_data;
181 char data_str[128];
182 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
183
184 send_data.ptr = data_str;
185 send_data.slen = 0;
186
187 pj_strcat(&send_data, &fe->cfg.prompt_str);
188 send_data.ptr[send_data.slen] = 0;
189
190 printf("%s", send_data.ptr);
191}
192
193static void send_err_arg(pj_cli_sess *sess,
194 const pj_cli_exec_info *info,
195 const pj_str_t *msg,
196 pj_bool_t with_return)
197{
198 pj_str_t send_data;
199 char data_str[256];
200 pj_size_t len;
201 unsigned i;
202 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
203
204 send_data.ptr = data_str;
205 send_data.slen = 0;
206
207 if (with_return)
208 pj_strcat2(&send_data, "\r\n");
209
210 len = fe->cfg.prompt_str.slen + info->err_pos;
211
212 for (i=0;i<len;++i) {
213 pj_strcat2(&send_data, " ");
214 }
215 pj_strcat2(&send_data, "^");
216 pj_strcat2(&send_data, "\r\n");
217 pj_strcat(&send_data, msg);
218 pj_strcat(&send_data, &fe->cfg.prompt_str);
219
220 send_data.ptr[send_data.slen] = 0;
221 printf("%s", send_data.ptr);
222}
223
224static void send_inv_arg(pj_cli_sess *sess,
225 const pj_cli_exec_info *info,
226 pj_bool_t with_return)
227{
228 static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28};
229 send_err_arg(sess, info, &ERR_MSG, with_return);
230}
231
232static void send_too_many_arg(pj_cli_sess *sess,
233 const pj_cli_exec_info *info,
234 pj_bool_t with_return)
235{
236 static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29};
237 send_err_arg(sess, info, &ERR_MSG, with_return);
238}
239
240static void send_hint_arg(pj_str_t *send_data,
241 const pj_str_t *desc,
242 pj_ssize_t cmd_len,
243 pj_ssize_t max_len)
244{
245 if ((desc) && (desc->slen > 0)) {
246 int j;
247
248 for (j=0;j<(max_len-cmd_len);++j) {
249 pj_strcat2(send_data, " ");
250 }
251 pj_strcat2(send_data, " ");
252 pj_strcat(send_data, desc);
253 send_data->ptr[send_data->slen] = 0;
254 printf("%s", send_data->ptr);
255 send_data->slen = 0;
256 }
257}
258
259static void send_ambi_arg(pj_cli_sess *sess,
260 const pj_cli_exec_info *info,
261 pj_bool_t with_return)
262{
263 unsigned i;
264 pj_size_t len;
265 pj_str_t send_data;
266 char data[1028];
267 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
268 const pj_cli_hint_info *hint = info->hint;
269 out_parse_state parse_state = OP_NORMAL;
270 pj_ssize_t max_length = 0;
271 pj_ssize_t cmd_length = 0;
272 const pj_str_t *cmd_desc = 0;
273 static const pj_str_t sc_type = {"sc", 2};
274 static const pj_str_t choice_type = {"choice", 6};
275 send_data.ptr = data;
276 send_data.slen = 0;
277
278 if (with_return)
279 pj_strcat2(&send_data, "\r\n");
280
281 len = fe->cfg.prompt_str.slen + info->err_pos;
282
283 for (i=0;i<len;++i) {
284 pj_strcat2(&send_data, " ");
285 }
286 pj_strcat2(&send_data, "^");
287 /* Get the max length of the command name */
288 for (i=0;i<info->hint_cnt;++i) {
289 if ((&hint[i].type) && (hint[i].type.slen > 0)) {
290 if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
291 if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) {
292 cmd_length += (hint[i].name.slen + 3);
293 } else {
294 cmd_length = hint[i].name.slen;
295 }
296 } else {
297 cmd_length = hint[i].name.slen;
298 }
299 } else {
300 cmd_length = hint[i].name.slen;
301 }
302
303 if (cmd_length > max_length) {
304 max_length = cmd_length;
305 }
306 }
307
308 cmd_length = 0;
309 for (i=0;i<info->hint_cnt;++i) {
310 if ((&hint[i].type) && (hint[i].type.slen > 0)) {
311 if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
312 parse_state = OP_SHORTCUT;
313 } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) {
314 parse_state = OP_CHOICE;
315 } else {
316 parse_state = OP_TYPE;
317 }
318 } else {
319 parse_state = OP_NORMAL;
320 }
321
322 if (parse_state != OP_SHORTCUT) {
323 pj_strcat2(&send_data, "\r\n ");
324 cmd_length = hint[i].name.slen;
325 }
326
327 switch (parse_state) {
328 case OP_CHOICE:
329 pj_strcat2(&send_data, "[");
330 pj_strcat(&send_data, &hint[i].name);
331 pj_strcat2(&send_data, "]");
332 break;
333 case OP_TYPE:
334 pj_strcat2(&send_data, "<");
335 pj_strcat(&send_data, &hint[i].type);
336 pj_strcat2(&send_data, ">");
337 break;
338 case OP_SHORTCUT:
339 /* Format : "Command | sc | description" */
340 {
341 cmd_length += hint[i].name.slen;
342 if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) {
343 pj_strcat2(&send_data, " | ");
344 cmd_length += 3;
345 } else {
346 pj_strcat2(&send_data, "\r\n ");
347 }
348 pj_strcat(&send_data, &hint[i].name);
349 }
350 break;
351 default:
352 pj_strcat(&send_data, &hint[i].name);
353 cmd_desc = &hint[i].desc;
354 break;
355 }
356
357 if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) ||
358 ((i+1) >= info->hint_cnt) ||
359 (pj_strncmp(&hint[i].desc, &hint[i+1].desc, hint[i].desc.slen)))
360 {
361 /* Add description info */
362 send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length);
363
364 cmd_length = 0;
365 }
366 }
367 pj_strcat2(&send_data, "\r\n");
368 pj_strcat(&send_data, &fe->cfg.prompt_str);
369 send_data.ptr[send_data.slen] = 0;
370 printf("%s", send_data.ptr);
371}
372
373static pj_bool_t handle_hint(pj_cli_sess *sess)
374{
375 pj_status_t status;
376 pj_bool_t retval = PJ_TRUE;
377
378 pj_pool_t *pool;
379 pj_cli_cmd_val *cmd_val;
380 pj_cli_exec_info info;
381 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
382 char *recv_buf = fe->input.buf;
383 pj_cli_t *cli = sess->fe->cli;
384
385 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_hint",
386 PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
387 NULL);
388
389 cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val);
390
391 status = pj_cli_sess_parse(sess, recv_buf, cmd_val,
392 pool, &info);
393
394 switch (status) {
395 case PJ_CLI_EINVARG:
396 send_inv_arg(sess, &info, PJ_TRUE);
397 break;
398 case PJ_CLI_ETOOMANYARGS:
399 send_too_many_arg(sess, &info, PJ_TRUE);
400 break;
401 case PJ_CLI_EMISSINGARG:
402 case PJ_CLI_EAMBIGUOUS:
403 send_ambi_arg(sess, &info, PJ_TRUE);
404 break;
405 case PJ_SUCCESS:
406 if (info.hint_cnt > 0) {
407 /* Compelete command */
408 send_ambi_arg(sess, &info, PJ_TRUE);
409 } else {
410 retval = PJ_FALSE;
411 }
412 break;
413 }
414
415 pj_pool_release(pool);
416 return retval;
417}
418
419static pj_bool_t handle_exec(pj_cli_sess *sess)
420{
421 pj_status_t status;
422 pj_bool_t retval = PJ_TRUE;
423
424 pj_pool_t *pool;
425 pj_cli_exec_info info;
426 pj_cli_t *cli = sess->fe->cli;
427 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
428 char *recv_buf = fe->input.buf;
429
430 printf("\r\n");
431
432 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_exec",
433 PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
434 NULL);
435
436 status = pj_cli_sess_exec(sess, recv_buf,
437 pool, &info);
438
439 switch (status) {
440 case PJ_CLI_EINVARG:
441 send_inv_arg(sess, &info, PJ_FALSE);
442 break;
443 case PJ_CLI_ETOOMANYARGS:
444 send_too_many_arg(sess, &info, PJ_FALSE);
445 break;
446 case PJ_CLI_EAMBIGUOUS:
447 case PJ_CLI_EMISSINGARG:
448 send_ambi_arg(sess, &info, PJ_FALSE);
449 break;
450 case PJ_CLI_EEXIT:
451 retval = PJ_FALSE;
452 break;
453 case PJ_SUCCESS:
454 send_prompt_str(sess);
455 break;
456 }
457
458 pj_pool_release(pool);
459 return retval;
460}
461
462static int readline_thread(void * p)
463{
464 struct cli_console_fe * fe = (struct cli_console_fe *)p;
465
466 printf("%s", fe->cfg.prompt_str.ptr);
467
468 while (!fe->thread_quit) {
469 pj_size_t input_len = 0;
470 pj_str_t input_str;
471 char *recv_buf = fe->input.buf;
472 pj_bool_t is_valid = PJ_TRUE;
473
474 if (fgets(recv_buf, fe->input.maxlen, stdin) == NULL) {
475 /*
476 * Be friendly to users who redirect commands into
477 * program, when file ends, resume with kbd.
478 * If exit is desired end script with q for quit
479 */
480 /* Reopen stdin/stdout/stderr to /dev/console */
481#if ((defined(PJ_WIN32) && PJ_WIN32!=0) || \
482 (defined(PJ_WIN64) && PJ_WIN64!=0)) && \
483 (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE==0)
484 if (freopen ("CONIN$", "r", stdin) == NULL) {
485#else
486 if (1) {
487#endif
488 puts("Cannot switch back to console from file redirection");
489 if (fe->cfg.quit_command.slen) {
490 pj_memcpy(recv_buf, fe->cfg.quit_command.ptr,
491 fe->input.maxlen);
492 }
493 recv_buf[fe->cfg.quit_command.slen] = '\0';
494 } else {
495 puts("Switched back to console from file redirection");
496 continue;
497 }
498 }
499
500 input_str.ptr = recv_buf;
501 input_str.slen = pj_ansi_strlen(recv_buf);
502 pj_strrtrim(&input_str);
503 recv_buf[input_str.slen] = '\n';
504 recv_buf[input_str.slen+1] = 0;
505 if (fe->thread_quit) {
506 break;
507 }
508 input_len = pj_ansi_strlen(fe->input.buf);
509 if ((input_len > 1) && (fe->input.buf[input_len-2] == '?')) {
510 fe->input.buf[input_len-1] = 0;
511 is_valid = handle_hint(fe->sess);
512 if (!is_valid)
513 printf("%s", fe->cfg.prompt_str.ptr);
514 } else {
515 is_valid = handle_exec(fe->sess);
516 }
517
518 pj_sem_post(fe->input.sem);
519 pj_sem_wait(fe->thread_sem);
520 }
521
522 return 0;
523}
524
525PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess,
526 char *buf,
527 unsigned maxlen)
528{
529 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
530
531 PJ_ASSERT_RETURN(sess, PJ_EINVAL);
532
533 fe->input.buf = buf;
534 fe->input.maxlen = maxlen;
535
536 if (!fe->input_thread) {
537 pj_status_t status;
538
539 status = pj_thread_create(fe->pool, NULL, &readline_thread, fe,
540 0, 0, &fe->input_thread);
541 if (status != PJ_SUCCESS)
542 return status;
543 } else {
544 /* Wake up readline thread */
545 pj_sem_post(fe->thread_sem);
546 }
547
548 pj_sem_wait(fe->input.sem);
549
550 return (pj_cli_is_quitting(fe->base.cli)? PJ_CLI_EEXIT : PJ_SUCCESS);
551}