| /* |
| <:copyright-BRCM:2016:DUAL/GPL:standard |
| |
| Broadcom Proprietary and Confidential.(c) 2016 Broadcom |
| All Rights Reserved |
| |
| Unless you and Broadcom execute a separate written software license |
| agreement governing use of this software, this software is licensed |
| to you under the terms of the GNU General Public License version 2 |
| (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, |
| with the following added to such license: |
| |
| As a special exception, the copyright holders of this software give |
| you permission to link this software with independent modules, and |
| to copy and distribute the resulting executable under terms of your |
| choice, provided that you also meet, for each linked independent |
| module, the terms and conditions of the license of that module. |
| An independent module is a module which is not derived from this |
| software. The special exception does not apply to any modifications |
| of the software. |
| |
| Not withstanding the above, under no circumstances may you combine |
| this software in any way with any other Broadcom software provided |
| under a license other than the GPL, without Broadcom's express prior |
| written consent. |
| |
| :> |
| */ |
| |
| |
| /******************************************************************* |
| * bcmcli_session.c |
| * |
| * CLI engine - session management |
| * |
| *******************************************************************/ |
| |
| #include <bcmos_system.h> |
| #include <bcmolt_utils.h> |
| |
| #define BCMCLI_INTERNAL |
| #include <bcmcli_session.h> |
| |
| static bcmos_fastlock session_lock; |
| static bcmcli_session *session_list; |
| static int session_module_initialized; |
| |
| /* |
| * Internal functions |
| */ |
| |
| static void _bcmcli_session_update_prompt(bcmcli_session *session) |
| { |
| if (!session) |
| return; |
| |
| if (session->parms.get_prompt) |
| { |
| session->parms.get_prompt(session, session->prompt_buf, BCMCLI_MAX_PROMPT_LEN); |
| session->prompt_buf[BCMCLI_MAX_PROMPT_LEN - 1] = '\0'; |
| } |
| else |
| { |
| session->prompt_buf[0] = '\0'; |
| } |
| } |
| |
| static char *_bcmcli_session_gets(bcmcli_session *session, char *buffer, uint32_t size) |
| { |
| const char *line = NULL; |
| _bcmcli_session_update_prompt(session); |
| #ifdef CONFIG_LIBEDIT |
| if (session && session->el && session->history) |
| { |
| int line_len; |
| line = (el_gets(session->el, &line_len)); |
| if (!line) |
| return NULL; |
| if (line_len > size) |
| { |
| bcmos_printf("%s: buffer is too short %u - got %d. Truncated\n", |
| __FUNCTION__, size, line_len); |
| } |
| strncpy(buffer, line, size); |
| if (*line && *line != '\n' && *line != '#') |
| history(session->history, &session->histevent, H_ENTER, line); |
| } |
| else |
| #endif |
| #ifdef CONFIG_LINENOISE |
| if (session && session->ln_session) |
| { |
| char *ln_line = linenoise(session->ln_session, session->prompt_buf, buffer, size); |
| if (ln_line) |
| { |
| if (strlen(ln_line)) |
| { |
| linenoiseHistoryAdd(session->ln_session, ln_line); /* Add to the history. */ |
| } |
| else |
| { |
| strncpy(buffer, "\n", size-1); |
| } |
| buffer[size-1] = 0; |
| line = buffer; |
| } |
| } |
| else |
| #endif |
| { |
| bcmcli_session_print(session, "%s", session->prompt_buf); |
| if (session && session->parms.gets) |
| line = session->parms.gets(session, buffer, size); |
| else |
| line = fgets(buffer, size, stdin); |
| } |
| return line ? buffer : NULL; |
| } |
| |
| #ifdef CONFIG_LIBEDIT |
| |
| static char *_bcmcli_editline_prompt(EditLine *e) |
| { |
| bcmcli_session *session = NULL; |
| el_get(e, EL_CLIENTDATA, &session); |
| BUG_ON(session == NULL || session->magic != BCMCLI_SESSION_MAGIC); |
| _bcmcli_session_update_prompt(session); |
| return session->prompt_buf; |
| } |
| |
| static int _bcmcli_editline_cfn(EditLine *el, char *c) |
| { |
| bcmcli_session *session = NULL; |
| char insert_buf[80]; |
| int c1; |
| bcmos_errno rc; |
| |
| el_get(el, EL_CLIENTDATA, &session); |
| BUG_ON(session == NULL || session->magic != BCMCLI_SESSION_MAGIC); |
| c1 = session->parms.get_char(session); |
| |
| /* ToDo: handle \t parameter extension */ |
| while (c1 > 0 && c1 == '\t') |
| { |
| |
| const LineInfo *li = el_line(el); |
| char *line = bcmos_alloc(li->cursor - li->buffer + 1); |
| if (!line) |
| continue; |
| memcpy(line, li->buffer, li->cursor - li->buffer); |
| line[li->cursor - li->buffer] = 0; |
| rc = bcmcli_extend(session, line, insert_buf, sizeof(insert_buf)); |
| bcmos_free(line); |
| if (rc) |
| { |
| c1 = session->parms.get_char(session); |
| continue; |
| } |
| el_insertstr(el, insert_buf); |
| printf("\r"); |
| el_set(el, EL_REFRESH, NULL); |
| c1 = session->parms.get_char(session); |
| } |
| if (c1 < 0) |
| return -1; |
| *c = c1; |
| return 1; |
| } |
| #endif |
| |
| /* linenoise line editing library: completion support */ |
| #ifdef CONFIG_LINENOISE |
| |
| static int _bcmcli_linenoise_read_char(long fd_in, char *c) |
| { |
| bcmcli_session *session = (bcmcli_session *)fd_in; |
| int c1; |
| c1 = session->parms.get_char(session); |
| if (c1 < 0) |
| { |
| return -1; |
| } |
| *c = c1; |
| return 1; |
| } |
| |
| static int _bcmcli_linenoise_write(long fd_out, const char *buf, size_t len) |
| { |
| bcmcli_session *session = (bcmcli_session *)fd_out; |
| /* Use a shortcut for len==1 - which is char-by-char input. |
| bcmos_printf("%*s", buf, 1) misbehaves on vxw platform, |
| possibly because it is too slow. |
| */ |
| if (len == 1 && !session->parms.write) |
| { |
| bcmos_putchar(buf[0]); |
| return 1; |
| } |
| return bcmcli_session_write(session, buf, len); |
| } |
| |
| static int _bcmcli_linenoise_tab(linenoiseSession *ln_session, const char *buf, int pos) |
| { |
| bcmcli_session *session = NULL; |
| char *line; |
| char insert_buf[80]=""; |
| bcmos_errno rc; |
| int len; |
| |
| session = linenoiseSessionData(ln_session); |
| BUG_ON(session == NULL || session->magic != BCMCLI_SESSION_MAGIC); |
| line = bcmos_alloc(strlen(buf)+1); |
| if (!line) |
| return 0; |
| strcpy(line, buf); |
| rc = bcmcli_extend(session, line, insert_buf, sizeof(insert_buf)); |
| bcmos_free(line); |
| if (rc || !strlen(insert_buf)) |
| return 0; |
| |
| len = strlen(buf); |
| line = bcmos_alloc(strlen(buf)+strlen(insert_buf)+1); |
| if (!line) |
| return 0; |
| if (pos >=0 && pos < len) |
| { |
| strncpy(line, buf, pos); |
| line[pos] = 0; |
| strcat(line, insert_buf); |
| strcat(line, &buf[pos]); |
| pos += strlen(insert_buf); |
| } |
| else |
| { |
| strcpy(line, buf); |
| strcat(line, insert_buf); |
| pos = strlen(line); |
| } |
| linenoiseSetBuffer(ln_session, line, pos); |
| bcmos_free(line); |
| return 1; |
| } |
| |
| #endif |
| |
| /* Default getc function */ |
| static int _bcmcli_session_get_char(bcmcli_session *session) |
| { |
| return bcmos_getchar(); |
| } |
| |
| /** Initialize session management module |
| * \return |
| * 0 =OK\n |
| * <0 =error code |
| */ |
| static void bcmcli_session_module_init(void) |
| { |
| bcmos_fastlock_init(&session_lock, 0); |
| session_module_initialized = 1; |
| } |
| |
| /** Open management session */ |
| int bcmcli_session_open_user(const bcmcli_session_parm *parm, bcmcli_session **p_session) |
| { |
| bcmcli_session *session; |
| bcmcli_session **p_last_next; |
| const char *name; |
| char *name_clone; |
| long flags; |
| int size; |
| |
| if (!p_session || !parm) |
| return BCM_ERR_PARM; |
| #ifndef CONFIG_EDITLINE |
| if (parm->line_edit_mode == BCMCLI_LINE_EDIT_ENABLE) |
| { |
| bcmos_trace(BCMOS_TRACE_LEVEL_ERROR, "Line editing feature is not compiled in. define CONFIG_EDITLINE\n"); |
| return BCM_ERR_NOT_SUPPORTED; |
| } |
| #endif |
| if (!session_module_initialized) |
| bcmcli_session_module_init(); |
| name = parm->name; |
| if (!name) |
| name = "*unnamed*"; |
| size = sizeof(bcmcli_session) + strlen(name) + 1 + parm->extra_size; |
| session=bcmos_calloc(size); |
| if (!session) |
| return BCM_ERR_NOMEM; |
| session->parms = *parm; |
| name_clone = (char *)session + sizeof(bcmcli_session) + parm->extra_size; |
| strcpy(name_clone, name); |
| session->parms.name = name_clone; |
| if (!session->parms.get_char) |
| session->parms.get_char = _bcmcli_session_get_char; |
| |
| #ifdef CONFIG_LIBEDIT |
| if (!parm->gets && (parm->line_edit_mode == BCMCLI_LINE_EDIT_ENABLE || |
| parm->line_edit_mode == BCMCLI_LINE_EDIT_DEFAULT)) |
| { |
| /* Initialize editline library */ |
| session->el = el_init(session->parms.name, stdin, stdout, stderr); |
| session->history = history_init(); |
| if (session->el && session->history) |
| { |
| el_set(session->el, EL_EDITOR, "emacs"); |
| el_set(session->el, EL_PROMPT, &_bcmcli_editline_prompt); |
| el_set(session->el, EL_TERMINAL, "xterm"); |
| el_set(session->el, EL_GETCFN, _bcmcli_editline_cfn); |
| el_set(session->el, EL_CLIENTDATA, session); |
| history(session->history, &session->histevent, H_SETSIZE, 800); |
| el_set(session->el, EL_HIST, history, session->history); |
| } |
| else |
| { |
| bcmos_trace(BCMOS_TRACE_LEVEL_ERROR, "Can't initialize editline library\n"); |
| bcmos_free(session); |
| return BCM_ERR_INTERNAL; |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_LINENOISE |
| /* Set the completion callback. This will be called every time the |
| * user uses the <tab> key. */ |
| if (!parm->gets && (parm->line_edit_mode == BCMCLI_LINE_EDIT_ENABLE || |
| parm->line_edit_mode == BCMCLI_LINE_EDIT_DEFAULT)) |
| { |
| linenoiseSessionIO io={.fd_in=(long)session, .fd_out=(long)session, |
| .read_char=_bcmcli_linenoise_read_char, |
| .write=_bcmcli_linenoise_write |
| }; |
| if (linenoiseSessionOpen(&io, session, &session->ln_session)) |
| { |
| bcmos_trace(BCMOS_TRACE_LEVEL_ERROR, "Can't create linenoise session\n"); |
| bcmos_free(session); |
| return BCM_ERR_INTERNAL; |
| } |
| linenoiseSetCompletionCallback(session->ln_session, _bcmcli_linenoise_tab); |
| } |
| #endif |
| |
| session->magic = BCMCLI_SESSION_MAGIC; |
| |
| flags = bcmos_fastlock_lock(&session_lock); |
| p_last_next = &session_list; |
| while(*p_last_next) |
| p_last_next = &((*p_last_next)->next); |
| *p_last_next = session; |
| bcmos_fastlock_unlock(&session_lock, flags); |
| |
| *p_session = session; |
| |
| return 0; |
| } |
| |
| static int bcmcli_session_string_write(bcmcli_session *session, const char *buf, uint32_t size) |
| { |
| bcmolt_string *str = bcmcli_session_user_priv(session); |
| return bcmolt_string_copy(str, buf, size); |
| } |
| |
| bcmos_errno bcmcli_session_open_string(bcmcli_session **session, bcmolt_string *str) |
| { |
| bcmcli_session_parm sp = { .user_priv = str, .write = bcmcli_session_string_write }; |
| |
| return bcmcli_session_open_user(&sp, session); |
| } |
| |
| /** Close management session. |
| * \param[in] session Session handle |
| */ |
| void bcmcli_session_close(bcmcli_session *session) |
| { |
| long flags; |
| |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| flags = bcmos_fastlock_lock(&session_lock); |
| if (session==session_list) |
| session_list = session->next; |
| else |
| { |
| bcmcli_session *prev = session_list; |
| while (prev && prev->next != session) |
| prev = prev->next; |
| if (!prev) |
| { |
| bcmos_fastlock_unlock(&session_lock, flags); |
| bcmos_trace(BCMOS_TRACE_LEVEL_ERROR, "%s: can't find session\n", __FUNCTION__); |
| return; |
| } |
| prev->next = session->next; |
| } |
| bcmos_fastlock_unlock(&session_lock, flags); |
| |
| #ifdef CONFIG_LIBEDIT |
| if (session->history) |
| history_end(session->history); |
| if (session->el) |
| el_end(session->el); |
| #endif |
| #ifdef CONFIG_LINENOISE |
| if (session->ln_session) |
| linenoiseSessionClose(session->ln_session); |
| #endif |
| session->magic = BCMCLI_SESSION_MAGIC_DEL; |
| bcmos_free(session); |
| |
| } |
| |
| /** Configure RAW input mode |
| * |
| * \param[in] session Session handle |
| * \param[in] is_raw TRUE=enable raw mode, FALSE=disable raw mode |
| * \return |
| * =0 - OK \n |
| * BCM_ERR_NOT_SUPPORTED - raw mode is not supported\n |
| */ |
| bcmos_errno bcmcli_session_raw_mode_set(bcmcli_session *session, bcmos_bool is_raw) |
| { |
| #ifdef CONFIG_LINENOISE |
| int rc; |
| if (session->parms.gets) |
| return BCM_ERR_NOT_SUPPORTED; |
| rc = linenoiseSetRaw(session->ln_session, is_raw); |
| return (rc == 0) ? BCM_ERR_OK : BCM_ERR_NOT_SUPPORTED; |
| #else |
| return BCM_ERR_NOT_SUPPORTED; |
| #endif |
| } |
| |
| /** Default write callback function |
| * write to stdout |
| */ |
| static int _bcmcli_session_write(bcmcli_session *session, const char *buf, uint32_t size) |
| { |
| return bcmos_printf("%.*s", size, buf); |
| } |
| |
| |
| /** Write function. |
| * Write buffer to the current session. |
| * \param[in] session Session handle. NULL=use stdout |
| * \param[in] buffer output buffer |
| * \param[in] size number of bytes to be written |
| * \return |
| * >=0 - number of bytes written\n |
| * <0 - output error |
| */ |
| int bcmcli_session_write(bcmcli_session *session, const char *buf, uint32_t size) |
| { |
| int (*write_cb)(bcmcli_session *session, const char *buf, uint32_t size); |
| if (session && session->parms.write) |
| { |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| write_cb = session->parms.write; |
| } |
| else |
| write_cb = _bcmcli_session_write; |
| return write_cb(session, buf, size); |
| } |
| |
| |
| /** Read line |
| * \param[in] session Session handle. NULL=use default |
| * \param[in,out] buf input buffer |
| * \param[in] size buf size |
| * \return |
| * buf if successful |
| * NULL if EOF or error |
| */ |
| char *bcmcli_session_gets(bcmcli_session *session, char *buf, uint32_t size) |
| { |
| return _bcmcli_session_gets(session, buf, size); |
| } |
| |
| |
| /** Print function. |
| * Prints in the context of current session. |
| * \param[in] session Session handle. NULL=use stdout |
| * \param[in] format print format - as in printf |
| * \param[in] ap parameters list. Undefined after the call |
| */ |
| void bcmcli_session_vprint(bcmcli_session *session, const char *format, va_list ap) |
| { |
| if (session && session->parms.write) |
| { |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| vsnprintf(session->outbuf, sizeof(session->outbuf), format, ap); |
| bcmcli_session_write(session, session->outbuf, strlen(session->outbuf)); |
| } |
| else |
| bcmos_vprintf(format, ap); |
| } |
| |
| |
| /** Print function. |
| * Prints in the context of current session. |
| * \param[in] session Session handle. NULL=use stdout |
| * \param[in] format print format - as in printf |
| */ |
| void bcmcli_session_print(bcmcli_session *session, const char *format, ...) |
| { |
| va_list ap; |
| va_start(ap, format); |
| bcmcli_session_vprint(session, format, ap); |
| va_end(ap); |
| } |
| |
| /** Get user_priv provoded in session partameters when it was registered |
| * \param[in] session Session handle. NULL=use stdin |
| * \return usr_priv value |
| */ |
| void *bcmcli_session_user_priv(bcmcli_session *session) |
| { |
| if (!session) |
| return NULL; |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| return session->parms.user_priv; |
| } |
| |
| |
| /** Get extra data associated with the session |
| * \param[in] session Session handle. NULL=default session |
| * \return extra_data pointer or NULL if there is no extra data |
| */ |
| void *bcmcli_session_data(bcmcli_session *session) |
| { |
| if (!session) |
| return NULL; |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| if (session->parms.extra_size <= 0) |
| return NULL; |
| return (char *)session + sizeof(*session); |
| } |
| |
| |
| /** Get session namedata |
| * \param[in] session Session handle. NULL=default session |
| * \return session name |
| */ |
| const char *bcmcli_session_name(bcmcli_session *session) |
| { |
| if (!session) |
| return NULL; |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| return session->parms.name; |
| } |
| |
| |
| /** Get session access righte |
| * \param[in] session Session handle. NULL=default debug session |
| * \return session access right |
| */ |
| bcmcli_access_right bcmcli_session_access_right(bcmcli_session *session) |
| { |
| if (!session) |
| return BCMCLI_ACCESS_DEBUG; |
| BUG_ON(session->magic != BCMCLI_SESSION_MAGIC); |
| return session->parms.access_right; |
| } |
| |
| /** Print buffer in hexadecimal format |
| * \param[in] session Session handle. NULL=use stdout |
| * \param[in] buffer Buffer address |
| * \param[in] offset Start offset in the buffer |
| * \param[in] count Number of bytes to dump |
| * \param[in] indent Optional indentation string |
| */ |
| void bcmcli_session_hexdump(bcmcli_session *session, const void *buffer, uint32_t offset, uint32_t count, const char *indent) |
| { |
| bcmos_hexdump((bcmos_msg_print_cb)bcmcli_session_print, session, buffer, offset, count, indent); |
| } |
| |
| /* |
| * Exports |
| */ |
| EXPORT_SYMBOL(bcmcli_session_open); |
| EXPORT_SYMBOL(bcmcli_session_close); |
| EXPORT_SYMBOL(bcmcli_session_write); |
| EXPORT_SYMBOL(bcmcli_session_vprint); |
| EXPORT_SYMBOL(bcmcli_session_print); |
| EXPORT_SYMBOL(bcmcli_session_access_right); |
| EXPORT_SYMBOL(bcmcli_session_data); |
| EXPORT_SYMBOL(bcmcli_session_name); |
| EXPORT_SYMBOL(bcmcli_session_hexdump); |