/* * Copyright (c) 2019 - 2024 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 "shellmatta_opt.h" #include "shellmatta_ymodem.h" #ifdef SHELLMATTA_TRANSPORT #include "shellmatta_transport.h" #endif #ifdef SHELLMATTA_AUTHENTICATION #include "shellmatta_auth.h" #endif #include #include #include #include #include /** * @brief processes the passed amount of data - called from the transport layer * @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 */ static shellmatta_retCode_t shellmatta_processDataInt(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_retCode_t cmdRet; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# in busy mode - keep calling this command */ if(NULL != inst->busyCmd) { /** -# just call the function until it is not busy anymore */ (void)shellmatta_opt_reInit(inst); ret = inst->busyCmd->cmdFct(handle, inst->buffer, inst->inputCount); if(SHELLMATTA_BUSY == ret) { /** -# do nothing - still busy */ } else if(SHELLMATTA_CONTINUE == ret) { inst->continuousCmd = inst->busyCmd; inst->busyCmd = NULL; } else { utils_terminateInput(inst); } } /** -# poll shellmatta ymomdem to send out the request to the sender */ if((0u == size) && (SHELLMATTA_YMODEM_INACTIVE != inst->ymodem.state)) { (void)shellmatta_ymodem_poll(handle); } /** -# call continuous function even if there is no data */ else if((0u == size) && (NULL != inst->continuousCmd)) { /** -# just call the function without any new data */ inst->stdinLength = 0u; inst->buffer[inst->stdinIdx] = '\0'; (void)shellmatta_opt_reInit(inst); ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount); if(SHELLMATTA_CONTINUE == ret) { /** -# do nothing just continue */ } else if(SHELLMATTA_BUSY == ret) { inst->busyCmd = inst->continuousCmd; inst->continuousCmd = NULL; } else { utils_terminateInput(inst); } } else { /* nothing to do here - continue parsing the command */ } /** -# process byte wise */ for (; (inst->byteCounter < size) && (NULL == inst->busyCmd); inst->byteCounter++) { /** -# handle ymodem when a session is active */ if (inst->ymodem.state != SHELLMATTA_YMODEM_INACTIVE) { ret = shellmatta_ymodem_processByte(handle, data[inst->byteCounter]); } /** -# in continuous mode - pass data directly to the command */ else if(NULL != inst->continuousCmd) { /** -# copy data and call command function */ inst->buffer[inst->stdinIdx] = data[inst->byteCounter]; inst->buffer[inst->stdinIdx + 1u] = '\0'; inst->stdinLength = 1u; (void)shellmatta_opt_reInit(inst); ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount); /** -# check if continuous mode is canceled or interrupted by busy mode */ if(SHELLMATTA_BUSY == ret) { inst->busyCmd = inst->continuousCmd; inst->continuousCmd = NULL; } else if(('\x03' == data[inst->byteCounter])) { /** -# cancel continue session */ utils_terminateInput(inst); ret = SHELLMATTA_OK; } else if(SHELLMATTA_CONTINUE == ret) { /** -# do nothing - continue */ } else { utils_terminateInput(inst); } } /** -# handle escape sequences */ else if(inst->escapeCounter != 0u) { escape_handleSequence(inst, data[inst->byteCounter]); } /** -# handle delimiter as start of processing the command */ else if (inst->delimiter == data[inst->byteCounter]) { 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 - add string delimiter to stop strstr from searching too far */ inst->buffer[inst->inputCount] = '\0'; 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->hereStartIdx = (uint32_t)(tempString - inst->buffer); inst->hereDelimiterIdx = inst->hereStartIdx + 2u; while((inst->hereDelimiterIdx < inst->inputCount) && ( ('\0' == inst->buffer[inst->hereDelimiterIdx]) || (' ' == inst->buffer[inst->hereDelimiterIdx]))) { inst->hereDelimiterIdx ++; } inst->hereLength = inst->inputCount - inst->hereDelimiterIdx; inst->dirty = true; utils_insertChars(inst, &data[inst->byteCounter], 1u); inst->lastNewlineIdx = inst->inputCount; } else { inst->hereLength = 0u; /** -# store the current command and reset the history buffer */ inst->dirty = true; history_storeCmd(inst); history_reset(inst); } } else { /** -# store the current command and reset the history buffer */ inst->dirty = true; history_storeCmd(inst); history_reset(inst); } } 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->buffer[inst->hereDelimiterIdx], tempString, inst->hereLength))) { /** -# store the current command and reset the history buffer */ inst->dirty = true; history_storeCmd(inst); history_reset(inst); /** -# process heredoc as stdin like input */ /** -# find start of heredoc data */ inst->stdinIdx = inst->hereDelimiterIdx + inst->hereLength; while( ('\n' == inst->buffer[inst->stdinIdx]) || ('\r' == inst->buffer[inst->stdinIdx])) { inst->stdinIdx ++; } /** -# calculate length and terminate stdin string */ if(inst->stdinIdx < inst->lastNewlineIdx) { inst->stdinLength = inst->lastNewlineIdx - inst->stdinIdx; inst->buffer[inst->lastNewlineIdx] = '\0'; } else { inst->stdinLength = 0u; } /** -# calculate length and terminate argument string */ inst->inputCount = inst->hereStartIdx; inst->buffer[inst->hereStartIdx] = '\0'; /** -# terminate heredoc session */ inst->hereLength = 0u; } else { /** -# the party goes on - just print the delimiter and store the position */ inst->lastNewlineIdx = inst->inputCount; utils_insertChars(inst, &data[inst->byteCounter], 1u); } } if(0u == inst->hereLength) { cmd = inst->cmdList; /** -# determine the cmd len (chars until first space or \0 is found */ cmdLen = 0u; while( (cmdLen < inst->inputCount) && (' ' != inst->buffer[cmdLen]) && ('\r' != inst->buffer[cmdLen]) && ('\n' != inst->buffer[cmdLen]) && ('\0' != inst->buffer[cmdLen])) { cmdLen ++; } /** -# search for a matching command */ while (NULL != cmd) { /** -# compare command and alias string and length */ if ( ((cmdLen == strlen(cmd->cmd)) && (0 == strncmp(inst->buffer, cmd->cmd, cmdLen))) || ((NULL != cmd->cmdAlias) && (cmdLen == strlen(cmd->cmdAlias)) && (0 == strncmp(inst->buffer, cmd->cmdAlias, cmdLen)))) { utils_writeEcho(inst, "\r\n", 2u); shellmatta_opt_init(inst, cmdLen + 1u); #ifdef SHELLMATTA_AUTHENTICATION cmdRet = SHELLMATTA_OK; if (SHELLMATTA_OK == shellmatta_auth_is_cmd_permitted(inst, cmd)) { cmdExecuted = 1u; cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount); } #else cmdExecuted = 1u; cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount); #endif switch(cmdRet) { case SHELLMATTA_CONTINUE: /** -# initialize stdin buffer and continuous cmd */ inst->stdinIdx = inst->bufferSize - 2u; inst->stdinLength = 0u; inst->continuousCmd = cmd; ret = cmdRet; break; case SHELLMATTA_BUSY: inst->busyCmd = cmd; ret = cmdRet; break; default: /* nothing to do - everything ok */ break; } cmd = NULL; } else { cmd = cmd->next; } } if ((0u == cmdExecuted) && (inst->inputCount > 0)) { if (inst->echoEnabled) { inst->write("\r\nCommand: ", 11u); inst->write(inst->buffer, inst->inputCount); inst->write(" not found", 10u); } else { inst->write("\r\nCommand not found!", 20u); } } /** -# terminate this session if no continuous mode is requested */ if( (NULL == inst->continuousCmd) && (NULL == inst->busyCmd)) { utils_terminateInput(inst); } } } /** -# ignore newline as first character (to be compatible to * terminals sending newline after return */ else if((0u == inst->inputCount) && ('\n' == data[inst->byteCounter])) { /* do nothing */ } /** -# check for tabulator key - auto complete */ else if('\t' == data[inst->byteCounter]) { inst->dirty = true; autocomplete_run(inst); } /** -# check for cancel - * terminate current input and print prompt again */ else if('\x03' == data[inst->byteCounter]) { inst->dirty = false; history_reset(inst); utils_terminateInput(inst); } /** -# check for backspace */ else if( ('\b' == data[inst->byteCounter]) || ('\x7f' == data[inst->byteCounter])) { inst->dirty = true; utils_removeChars(inst, 1u, true); } /** -# check for start of escape sequence */ else if('\x1b' == data[inst->byteCounter]) { inst->escapeCounter = 1u; } else { inst->dirty = true; utils_insertChars(inst, &data[inst->byteCounter], 1u); } /** -# reset tab counter on not a tab */ if ('\t' != data[inst->byteCounter]) { inst->tabCounter = 0u; } } /** -# initialize the byte buffer if processing of the input is finished */ if(ret != SHELLMATTA_BUSY) { inst->byteCounter = 0u; } return ret; } /** * @} * @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))) { /** -# clear the shellmatta instance */ memset((void *)inst, 0, sizeof(shellmatta_instance_t)); /** -# copy all provided buffers into the shellmatta instance */ inst->buffer = buffer; inst->bufferSize = bufferSize; inst->historyBuffer = historyBuffer; inst->historyBufferSize = historyBufferSize; inst->historyReadUp = true; inst->write = writeFct; inst->prompt = prompt; inst->echoEnabled = true; inst->dirty = false; inst->delimiter = '\r'; inst->mode = SHELLMATTA_MODE_INSERT; inst->cmdList = &(inst->helpCmd); shellmatta_opt_init(inst, 0u); /** -# copy the help command structure to this instance */ memcpy(&(inst->helpCmd), &helpCmd, sizeof(shellmatta_cmd_t)); #ifdef SHELLMATTA_AUTHENTICATION /** -# copy the auth commands to the instance */ memcpy(&(inst->loginCmd), &shellmatta_auth_loginCmd, sizeof(shellmatta_cmd_t)); memcpy(&(inst->logoutCmd), &shellmatta_auth_logoutCmd, sizeof(shellmatta_cmd_t)); #endif if(NULL != cmdList) { #ifndef SHELLMATTA_AUTHENTICATION inst->helpCmd.next = (shellmatta_cmd_t *) cmdList; #else inst->logoutCmd.next = (shellmatta_cmd_t *) cmdList; #endif inst->cmdListIsConst = true; } inst->magic = SHELLMATTA_MAGIC; *handle = (shellmatta_handle_t)inst; #ifdef SHELLMATTA_TRANSPORT /* init transport layer */ inst->transportBusyMark = 0u; shellmatta_transport_init(&inst->transportLayer, inst->write); #endif /** -# print the first prompt */ utils_terminateInput(inst); } return SHELLMATTA_OK; } /** * @brief resets the whole shellmatta instance * @param[in] handle shellmatta instance handle * @param[in] printPrompt print a new command prompt * * This function can be used e.g. when working with connection based interfaces (e.g. sockets) to clear * the shell from old content when a new connection is opened. * It resets all internal states - the buffers are left as they are - they will be overwritten. * The history buffer is deleted as well. */ shellmatta_retCode_t shellmatta_resetShell(shellmatta_handle_t handle, bool printPrompt) { shellmatta_instance_t *inst = (shellmatta_instance_t *)handle; shellmatta_retCode_t ret = SHELLMATTA_OK; /** -# check if the instance is plausible */ if( (NULL != handle) && (SHELLMATTA_MAGIC == inst->magic)) { inst->inputCount = 0u; inst->byteCounter = 0u; inst->continuousCmd = NULL; inst->busyCmd = NULL; inst->lastNewlineIdx = 0u; inst->cursor = 0u; inst->stdinIdx = 0u; inst->stdinLength = 0u; inst->historyStart = 0u; inst->historyEnd = 0u; inst->historyRead = 0u; inst->historyReadUp = true; inst->dirty = false; inst->tabCounter = 0u; inst->escapeCounter = 0u; inst->hereStartIdx = 0u; inst->hereDelimiterIdx = 0u; inst->hereLength = 0u; inst->ymodem.state = SHELLMATTA_YMODEM_INACTIVE; shellmatta_opt_init(inst, 0u); #ifdef SHELLMATTA_AUTHENTICATION inst->userId = 0u; inst->userPointer = NULL; #endif if(true == printPrompt) { /** -# print a prompt if requested */ utils_terminateInput(inst); } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @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 * * The cmd name is mandatory, the rest of the command parameters (alias, helpText and usageText) are optional * and can be set to NULL if not used. */ 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; int aliasDiff; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && (false == inst->cmdListIsConst) && (NULL != cmd) && (NULL != cmd->cmd)) { 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); if( (NULL != cmd->cmdAlias) && (NULL != tempCmd->cmdAlias)) { aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias); } else { aliasDiff = 1; } /** -# check for a duplicate command */ if((0u == cmdDiff) || (0u == aliasDiff)) { ret = SHELLMATTA_DUPLICATE; } else if(cmdDiff > 0) { 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; } } #ifdef SHELLMATTA_AUTHENTICATION /** -# append permissions to added command if any */ if (inst->permList) { /** -# Search for command in perm list */ for (uint32_t i = 0u; i < inst->permListLength; i++) { if (0 == strcmp(cmd->cmd, inst->permList[i].cmd)) { cmd->authLink = &inst->permList[i]; break; } } } #endif } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief removes a command from the command list * @param[in] handle shellmatta instance handle * @param[in] cmd pointer to the command to remove type #shellmatta_cmd_t * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, const shellmatta_cmd_t *cmd) { shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; shellmatta_cmd_t *prevCmd; shellmatta_cmd_t *tempCmd; shellmatta_retCode_t ret = SHELLMATTA_OK; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && (false == inst->cmdListIsConst) && (NULL != cmd) && (NULL != cmd->cmd)) { tempCmd = inst->cmdList; prevCmd = NULL; /** -# loop through command list */ while(NULL != tempCmd) { /** -# compare command strings to find the command to delete */ if (0 == strcmp(tempCmd->cmd, cmd->cmd) && (strlen(tempCmd->cmd) == strlen(cmd->cmd))) { /** -# first command removed */ if(NULL == prevCmd) { inst->cmdList = tempCmd->next; } /** -# last command removed */ else if(NULL == tempCmd->next) { prevCmd->next = NULL; } /** -# command removed from the middle of the list */ else { prevCmd->next = tempCmd->next; } break; } prevCmd = tempCmd; tempCmd = tempCmd->next; } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief changes configuration of a shellmatta instance * @param[in] handle shellmatta instance handle * @param[in] mode insert mode of the shellmatta type #shellmatta_mode_t * @param[in] echoEnabled true: echo received chars to the output * @param[in] delimiter delimiter used to detect the end of a cmd (default "\r") * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_configure( shellmatta_handle_t handle, shellmatta_mode_t mode, bool echoEnabled, char delimiter) { shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; shellmatta_retCode_t ret = SHELLMATTA_OK; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && ((SHELLMATTA_MODE_INSERT == mode) || (SHELLMATTA_MODE_OVERWRITE == mode))) { inst->mode = mode; inst->echoEnabled = echoEnabled; inst->delimiter = delimiter; } 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_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; #ifdef SHELLMATTA_TRANSPORT char *tmpData; uint32_t tmpSize = 0; uint32_t i; bool processingDone = false; #endif /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { #ifdef SHELLMATTA_TRANSPORT for (i = inst->transportBusyMark; i < size; i ++) { ret = shellmatta_transport_process(&inst->transportLayer, data[i], &tmpData, &tmpSize); if (SHELLMATTA_OK == ret) { ret = shellmatta_processDataInt(handle, tmpData, tmpSize); processingDone = true; if (SHELLMATTA_BUSY == ret) { inst->transportBusyMark = i; break; } else { inst->transportBusyMark = 0u; } } else if (SHELLMATTA_ERROR == ret) { utils_writeEcho(inst, "crc error\r\n", 11); utils_terminateInput(inst); } else { /* nothing to do - transport layer busy */ } } /*! -# call the internal processing at least once - for continued and busy commands */ if (true != processingDone) { ret = shellmatta_processDataInt(handle, tmpData, 0); } if (false == inst->transportLayer.disableAutoFlush) { (void)shellmatta_transport_flush(handle); } #else ret = shellmatta_processDataInt(handle, data, size); #endif } 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 = SHELLMATTA_WRITE(data, length); } return ret; } /** * @brief reads the stdin like buffer * @param[in] handle shellmatta instance handle * @param[out] data stdin data or NULL * @param[out] length size of the stdin data * @return */ shellmatta_retCode_t shellmatta_read( 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) && (NULL != data) && (NULL != length)) { /** -# return a pointer to the data or NULL */ if(0u == inst->stdinLength) { *data = NULL; } else { *data = &(inst->buffer[inst->stdinIdx]); } *length = inst->stdinLength; } 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 { SHELLMATTA_WRITE(outputBuffer, length); } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } #endif /** @} */