/* * Copyright (c) 2019 Stefan Strobel * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * @file shellmatta.c * @brief Main implementation of the Shellmatta terminal implementation * @author Stefan Strobel */ /** * @addtogroup shellmatta_private * @{ */ #include "shellmatta.h" #include "shellmatta_autocomplete.h" #include "shellmatta_history.h" #include "shellmatta_utils.h" #include "shellmatta_escape.h" #include #include #include #include /** * @} * @addtogroup shellmatta_api * @{ */ /** * @brief initialize the shellmatta terminal and provide all callbacks * @param[in,out] inst pointer to a shellmatta instance * @param[out] handle pointer to shellmatta handle - * has to be used for all furterh api calls * @param[in] buffer pointer to the input buffer to use * @param[in] bufferSize size of the provided input buffer * @param[in] historyBuffer pointer to the history buffer to use * NULL in case of no history buffer * @param[in] historyBufferSize size of the history buffer * 0 in case of no history buffer * @param[in] prompt pointer to prompt string - is printed * after each command * @param[in] cmdList constant command list if no dynamic * adding of commands is desired * @param[in] writeFct function pointer to output function */ shellmatta_retCode_t shellmatta_doInit( shellmatta_instance_t *inst, shellmatta_handle_t *handle, char *buffer, uint32_t bufferSize, char *historyBuffer, uint32_t historyBufferSize, const char *prompt, const shellmatta_cmd_t *cmdList, shellmatta_write_t writeFct) { /** -# check parameters for plausibility */ if( (NULL != inst) && (NULL != handle) && (NULL != buffer) && (0u != bufferSize) && (NULL != prompt) && (NULL != writeFct) && ((NULL != historyBuffer) || (0u == historyBufferSize))) { /** -# copy all provided buffers into the shellmatta instance */ inst->buffer = buffer; inst->bufferSize = bufferSize; inst->inputCount = 0u; inst->lastNewlineIdx = 0u; inst->cursor = 0u; inst->historyBuffer = historyBuffer; inst->historyBufferSize = historyBufferSize; inst->historyStart = 0u; inst->historyEnd = 0u; inst->historyRead = 0u; inst->historyReadUp = true; inst->write = writeFct; inst->prompt = prompt; inst->echoEnabled = true; inst->dirty = false; inst->tabCounter = 0u; inst->escapeCounter = 0u; inst->hereLength = 0u; inst->mode = SHELLMATTA_MODE_INSERT; inst->cmdList = &helpCmd; inst->cmdListIsConst = false; if(NULL != cmdList) { helpCmd.next = (shellmatta_cmd_t *) cmdList; inst->cmdListIsConst = true; } inst->magic = SHELLMATTA_MAGIC; *handle = (shellmatta_handle_t)inst; /** -# print the first prompt */ utils_terminateInput(inst); } return SHELLMATTA_OK; } /** * @brief adds a command to the command list alphabetically ordered * @param[in] handle shellmatta instance handle * @param[in] cmd pointer to the command to add type #shellmatta_cmd_t * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) * SHELLMATTA_DUPLICATE */ shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd) { shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; shellmatta_cmd_t *tempCmd; shellmatta_cmd_t **prevCmd; bool cmdPlaced = false; shellmatta_retCode_t ret = SHELLMATTA_OK; int cmdDiff = 0; int aliasDiff = 0; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && (false == inst->cmdListIsConst)) { tempCmd = inst->cmdList; prevCmd = &inst->cmdList; /** -# register first command as list entry */ if (NULL == tempCmd) { inst->cmdList = cmd; cmd->next = NULL; } /** -# append the new command sorted */ else { while ((false == cmdPlaced) && (SHELLMATTA_OK == ret)) { cmdDiff = strcmp(tempCmd->cmd, cmd->cmd); aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias); /** -# check for a duplicate command */ if((0u == cmdDiff) || (0u == aliasDiff)) { ret = SHELLMATTA_DUPLICATE; } else if(0 < cmdDiff) { cmd->next = tempCmd; *prevCmd = cmd; cmdPlaced = true; } else if(NULL == tempCmd->next) { tempCmd->next = cmd; cmd->next = NULL; cmdPlaced = true; } else { /* nothing to do */ } prevCmd = &(tempCmd->next); tempCmd = tempCmd->next; } } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief processes the passed amount of data * @param[in] handle shellmatta instance handle * @param[in] data pointer to input data to process * @param[in] size length of input data to process * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT */ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, char *data, uint32_t size) { shellmatta_cmd_t *cmd; uint8_t cmdExecuted = 0u; uint32_t cmdLen; char *tempString; shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { /** -# process byte wise */ for (uint32_t i = 0u; i < size; i++) { /** -# handle escape sequences */ if(inst->escapeCounter != 0u) { escape_handleSequence(inst, *data); } /** -# ignore newline as first character (to be compatible to * terminals sending newline after return */ else if((0u == inst->inputCount) && ('\n' == *data)) { /* do nothing */ } /** -# handle return as start of processing the command */ else if ('\r' == *data) { if(0u == inst->hereLength) { /** * \dot * digraph heredocParser { * start -> wait [ label="<< in first line - store delimiter" ]; * wait -> wait [ label="delimiter not detected" ]; * wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ]; * } * \enddot */ /** -# check for heredoc */ tempString = strstr(inst->buffer, "<<"); if(NULL != tempString) { /*' -# check if length of heredoc delimiter is valid */ if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u)) { inst->hereLength = inst->inputCount - ((uint32_t)(tempString - inst->buffer) + 2u); if(sizeof(inst->hereDelimiter) < inst->hereLength) { inst->write("\r\nHeredoc delimiter too long\r\n", 30u); inst->inputCount = 0u; inst->hereLength = 0u; } else { /** -# store delimiter and remove it from the input buffer */ strncpy(inst->hereDelimiter, &(tempString[2u]), inst->hereLength); inst->inputCount -= (inst->hereLength + 2u); inst->cursor = inst->inputCount; inst->dirty = true; utils_insertChars(inst, data, 1); inst->lastNewlineIdx = inst->inputCount; } } else { inst->hereLength = 0u; } } } else { tempString = &inst->buffer[inst->lastNewlineIdx]; cmdLen = inst->inputCount - inst->lastNewlineIdx; /** -# skip newline characters before comparison */ while(('\n' == *tempString) || ('\r' == *tempString)) { tempString ++; cmdLen --; } if( (inst->hereLength == cmdLen) && (0 == strncmp( inst->hereDelimiter, tempString, inst->hereLength))) { inst->inputCount = inst->lastNewlineIdx; inst->hereLength = 0u; } else { inst->lastNewlineIdx = inst->inputCount; utils_insertChars(inst, data, 1); } } if(0u == inst->hereLength) { cmd = inst->cmdList; inst->buffer[inst->inputCount] = 0u; /** -# store the current command and reset the history buffer */ inst->dirty = true; history_storeCmd(inst); history_reset(inst); /** -# determine the cmd len (chars until first space or \0 is found */ cmdLen = 0u; while( (cmdLen < inst->inputCount) && (' ' != inst->buffer[cmdLen]) && ('\0' != inst->buffer[cmdLen])) { cmdLen ++; } /** -# search for a matching command */ while (NULL != cmd) { /** -# compare command string and length */ if ( ((0 == strncmp( inst->buffer, cmd->cmd, cmdLen)) && (cmdLen == strlen(cmd->cmd))) || ((0 == strncmp( inst->buffer, cmd->cmdAlias, cmdLen)) && (cmdLen == strlen(cmd->cmdAlias)))) { inst->write("\r\n", 2u); cmdExecuted = 1u; cmd->cmdFct(inst, inst->buffer, inst->inputCount); cmd = NULL; } else { cmd = cmd->next; } } if ((cmdExecuted == 0u) && (inst->inputCount > 0)) { inst->buffer[inst->inputCount] = '\0'; inst->write("\r\nCommand: ", 11u); inst->write(inst->buffer, inst->inputCount); inst->write(" not found", 10u); } utils_terminateInput(inst); } } /** -# check for tabulator key - auto complete */ else if('\t' == *data) { inst->dirty = true; autocomplete_run(inst); } /** -# check for cancel - * terminate current input and print prompt again */ else if(3 == *data) { inst->dirty = false; history_reset(inst); utils_terminateInput(inst); } /** -# check for backspace */ else if('\b' == *data) { inst->dirty = true; utils_removeChars(inst, 1u, true); } /** -# check for delete key */ else if(0x7eu == *data) { inst->dirty = true; utils_removeChars(inst, 1u, false); } /** -# check for start of escape sequence */ else if('\e' == *data) { inst->escapeCounter = 1u; } else { inst->dirty = true; utils_insertChars(inst, data, 1); } /** -# reset tab counter on not a tab */ if ('\t' != *data) { inst->tabCounter = 0u; } data ++; } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief simple write function to write a datastream to the output of an instance * @param[in] handle shellmatta instance handle * @param[in] data data to be written * @param[in] length amount of data to be written * @return */ shellmatta_retCode_t shellmatta_write( shellmatta_handle_t handle, char *data, uint32_t length) { shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { /** -# pass the data to the write function of the instance */ ret = inst->write(data, length); } return ret; } #ifndef SHELLMATTA_STRIP_PRINTF /** * @brief printf like function to print output to the instances output * @param[in] handle shellmatta instance handle * @param[in] fmt format string and parameters * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (no valid instance) * #SHELLMATTA_ERROR (buffer overflow or format err) */ shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle, const char *fmt, ...) { char outputBuffer[SHELLMATTA_OUTPUT_BUFFER_SIZE]; va_list arg; int length; shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { /** -# assemble output string and write it */ va_start(arg, fmt); length = vsnprintf(outputBuffer, SHELLMATTA_OUTPUT_BUFFER_SIZE, fmt, arg); va_end(arg); if(length < 0) { ret = SHELLMATTA_ERROR; } else { inst->write(outputBuffer, length); } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } #endif /** @} */