shellmatta.c 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /*
  2. * Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
  3. *
  4. * This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  7. */
  8. /**
  9. * @file shellmatta.c
  10. * @brief Main implementation of the Shellmatta terminal implementation
  11. * @author Stefan Strobel <stefan.strobel@shimatta.net>
  12. */
  13. /**
  14. * @addtogroup shellmatta_private
  15. * @{
  16. */
  17. #include "shellmatta.h"
  18. #include "shellmatta_autocomplete.h"
  19. #include "shellmatta_history.h"
  20. #include "shellmatta_utils.h"
  21. #include "shellmatta_escape.h"
  22. #include <stddef.h>
  23. #include <string.h>
  24. #include <stdarg.h>
  25. #include <stdio.h>
  26. /**
  27. * @}
  28. * @addtogroup shellmatta_api
  29. * @{
  30. */
  31. /**
  32. * @brief initialize the shellmatta terminal and provide all callbacks
  33. * @param[in,out] inst pointer to a shellmatta instance
  34. * @param[out] handle pointer to shellmatta handle -
  35. * has to be used for all furterh api calls
  36. * @param[in] buffer pointer to the input buffer to use
  37. * @param[in] bufferSize size of the provided input buffer
  38. * @param[in] historyBuffer pointer to the history buffer to use
  39. * NULL in case of no history buffer
  40. * @param[in] historyBufferSize size of the history buffer
  41. * 0 in case of no history buffer
  42. * @param[in] prompt pointer to prompt string - is printed
  43. * after each command
  44. * @param[in] cmdList constant command list if no dynamic
  45. * adding of commands is desired
  46. * @param[in] writeFct function pointer to output function
  47. */
  48. shellmatta_retCode_t shellmatta_doInit(
  49. shellmatta_instance_t *inst,
  50. shellmatta_handle_t *handle,
  51. char *buffer,
  52. uint32_t bufferSize,
  53. char *historyBuffer,
  54. uint32_t historyBufferSize,
  55. const char *prompt,
  56. const shellmatta_cmd_t *cmdList,
  57. shellmatta_write_t writeFct)
  58. {
  59. /** -# check parameters for plausibility */
  60. if( (NULL != inst)
  61. && (NULL != handle)
  62. && (NULL != buffer)
  63. && (0u != bufferSize)
  64. && (NULL != prompt)
  65. && (NULL != writeFct)
  66. && ((NULL != historyBuffer) || (0u == historyBufferSize)))
  67. {
  68. /** -# copy all provided buffers into the shellmatta instance */
  69. inst->buffer = buffer;
  70. inst->bufferSize = bufferSize;
  71. inst->inputCount = 0u;
  72. inst->lastNewlineIdx = 0u;
  73. inst->cursor = 0u;
  74. inst->historyBuffer = historyBuffer;
  75. inst->historyBufferSize = historyBufferSize;
  76. inst->historyStart = 0u;
  77. inst->historyEnd = 0u;
  78. inst->historyRead = 0u;
  79. inst->historyReadUp = true;
  80. inst->write = writeFct;
  81. inst->prompt = prompt;
  82. inst->echoEnabled = true;
  83. inst->dirty = false;
  84. inst->tabCounter = 0u;
  85. inst->escapeCounter = 0u;
  86. inst->hereStartIdx = 0u;
  87. inst->hereDelimiterIdx = 0u;
  88. inst->hereLength = 0u;
  89. inst->mode = SHELLMATTA_MODE_INSERT;
  90. inst->cmdList = &helpCmd;
  91. inst->cmdListIsConst = false;
  92. if(NULL != cmdList)
  93. {
  94. helpCmd.next = (shellmatta_cmd_t *) cmdList;
  95. inst->cmdListIsConst = true;
  96. }
  97. inst->magic = SHELLMATTA_MAGIC;
  98. *handle = (shellmatta_handle_t)inst;
  99. /** -# print the first prompt */
  100. utils_terminateInput(inst);
  101. }
  102. return SHELLMATTA_OK;
  103. }
  104. /**
  105. * @brief resets the whole shellmatta instance
  106. * @param[in] handle shellmatta instance handle
  107. * @param[in] printPrompt print a new command prompt
  108. *
  109. * This function can be used e.g. when working with connection based interfaces (e.g. sockets) to clear
  110. * the shell from old content when a new connection is opened.
  111. * It resets all internal states - the buffers are left as they are - they will be overwritten.
  112. * The history buffer is deleted as well.
  113. */
  114. shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool printPrompt)
  115. {
  116. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  117. shellmatta_retCode_t ret = SHELLMATTA_OK;
  118. /*! -# check if the instance is plausible */
  119. if( (NULL != handle)
  120. && (SHELLMATTA_MAGIC == inst->magic))
  121. {
  122. inst->inputCount = 0u;
  123. inst->lastNewlineIdx = 0u;
  124. inst->cursor = 0u;
  125. inst->historyStart = 0u;
  126. inst->historyEnd = 0u;
  127. inst->historyRead = 0u;
  128. inst->historyReadUp = true;
  129. inst->dirty = false;
  130. inst->tabCounter = 0u;
  131. inst->escapeCounter = 0u;
  132. inst->hereStartIdx = 0u;
  133. inst->hereDelimiterIdx = 0u;
  134. inst->hereLength = 0u;
  135. if(true == printPrompt)
  136. {
  137. /** -# print a prompt if requested */
  138. utils_terminateInput(inst);
  139. }
  140. }
  141. else
  142. {
  143. ret = SHELLMATTA_USE_FAULT;
  144. }
  145. return ret;
  146. }
  147. /**
  148. * @brief adds a command to the command list alphabetically ordered
  149. * @param[in] handle shellmatta instance handle
  150. * @param[in] cmd pointer to the command to add type #shellmatta_cmd_t
  151. * @return errorcode #SHELLMATTA_OK
  152. * #SHELLMATTA_USE_FAULT (param err)
  153. * SHELLMATTA_DUPLICATE
  154. *
  155. * The cmd name is mandatory, the rest of the command parameters (alias, helpText and usageText) are optional
  156. * and can be set to NULL if not used.
  157. */
  158. shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
  159. {
  160. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  161. shellmatta_cmd_t *tempCmd;
  162. shellmatta_cmd_t **prevCmd;
  163. bool cmdPlaced = false;
  164. shellmatta_retCode_t ret = SHELLMATTA_OK;
  165. int cmdDiff = 0;
  166. int aliasDiff = 0;
  167. /** -# check parameters for plausibility */
  168. if( (NULL != inst)
  169. && (SHELLMATTA_MAGIC == inst->magic)
  170. && (false == inst->cmdListIsConst)
  171. && (NULL != cmd)
  172. && (NULL != cmd->cmd))
  173. {
  174. tempCmd = inst->cmdList;
  175. prevCmd = &inst->cmdList;
  176. /** -# register first command as list entry */
  177. if (NULL == tempCmd)
  178. {
  179. inst->cmdList = cmd;
  180. cmd->next = NULL;
  181. }
  182. /** -# append the new command sorted */
  183. else
  184. {
  185. while ((false == cmdPlaced) && (SHELLMATTA_OK == ret))
  186. {
  187. cmdDiff = strcmp(tempCmd->cmd, cmd->cmd);
  188. if( (NULL != cmd->cmdAlias)
  189. && (NULL != tempCmd->cmdAlias))
  190. {
  191. aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias);
  192. }
  193. else
  194. {
  195. aliasDiff = 1;
  196. }
  197. /** -# check for a duplicate command */
  198. if((0u == cmdDiff) || (0u == aliasDiff))
  199. {
  200. ret = SHELLMATTA_DUPLICATE;
  201. }
  202. else if(0 < cmdDiff)
  203. {
  204. cmd->next = tempCmd;
  205. *prevCmd = cmd;
  206. cmdPlaced = true;
  207. }
  208. else if(NULL == tempCmd->next)
  209. {
  210. tempCmd->next = cmd;
  211. cmd->next = NULL;
  212. cmdPlaced = true;
  213. }
  214. else
  215. {
  216. /* nothing to do */
  217. }
  218. prevCmd = &(tempCmd->next);
  219. tempCmd = tempCmd->next;
  220. }
  221. }
  222. }
  223. else
  224. {
  225. ret = SHELLMATTA_USE_FAULT;
  226. }
  227. return ret;
  228. }
  229. /**
  230. * @brief removes a command from the command list
  231. * @param[in] handle shellmatta instance handle
  232. * @param[in] cmd pointer to the command to remove type #shellmatta_cmd_t
  233. * @return errorcode #SHELLMATTA_OK
  234. * #SHELLMATTA_USE_FAULT (param err)
  235. */
  236. shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
  237. {
  238. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  239. shellmatta_cmd_t *prevCmd;
  240. shellmatta_cmd_t *tempCmd;
  241. shellmatta_retCode_t ret = SHELLMATTA_OK;
  242. /** -# check parameters for plausibility */
  243. if( (NULL != inst)
  244. && (SHELLMATTA_MAGIC == inst->magic)
  245. && (false == inst->cmdListIsConst)
  246. && (NULL != cmd)
  247. && (NULL != cmd->cmd))
  248. {
  249. tempCmd = inst->cmdList;
  250. prevCmd = NULL;
  251. /*! -# loop through command list */
  252. while(NULL != tempCmd)
  253. {
  254. /*! -# compare command strings to find the command to delete */
  255. if (0 == strcmp( tempCmd->cmd,
  256. cmd->cmd)
  257. && (strlen(tempCmd->cmd) == strlen(cmd->cmd)))
  258. {
  259. /*! -# first command removed */
  260. if(NULL == prevCmd)
  261. {
  262. inst->cmdList = tempCmd->next;
  263. }
  264. /*! -# last command removed */
  265. else if(NULL == tempCmd->next)
  266. {
  267. prevCmd->next = NULL;
  268. }
  269. /*! -# command removed from the middle of the list */
  270. else
  271. {
  272. prevCmd->next = tempCmd->next;
  273. }
  274. break;
  275. }
  276. prevCmd = tempCmd;
  277. tempCmd = tempCmd->next;
  278. }
  279. }
  280. else
  281. {
  282. ret = SHELLMATTA_USE_FAULT;
  283. }
  284. return ret;
  285. }
  286. /**
  287. * @brief changes configuration of a shellmatta instance
  288. * @param[in] handle shellmatta instance handle
  289. * @param[in] mode insert mode of the shellmatta type #shellmatta_mode_t
  290. * @param[in] echoEnabled true: echo received chars to the output
  291. * @return errorcode #SHELLMATTA_OK
  292. * #SHELLMATTA_USE_FAULT (param err)
  293. */
  294. shellmatta_retCode_t shellmatta_configure(shellmatta_handle_t handle, shellmatta_mode_t mode, bool echoEnabled)
  295. {
  296. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  297. shellmatta_retCode_t ret = SHELLMATTA_OK;
  298. /** -# check parameters for plausibility */
  299. if( (NULL != inst)
  300. && (SHELLMATTA_MAGIC == inst->magic)
  301. && ((mode == SHELLMATTA_MODE_INSERT) || (mode == SHELLMATTA_MODE_OVERWRITE)))
  302. {
  303. inst->mode = mode;
  304. inst->echoEnabled = echoEnabled;
  305. }
  306. else
  307. {
  308. ret = SHELLMATTA_USE_FAULT;
  309. }
  310. return ret;
  311. }
  312. /**
  313. * @brief processes the passed amount of data
  314. * @param[in] handle shellmatta instance handle
  315. * @param[in] data pointer to input data to process
  316. * @param[in] size length of input data to process
  317. * @return errorcode #SHELLMATTA_OK
  318. * #SHELLMATTA_USE_FAULT
  319. */
  320. shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle,
  321. char *data,
  322. uint32_t size)
  323. {
  324. shellmatta_cmd_t *cmd;
  325. uint8_t cmdExecuted = 0u;
  326. uint32_t cmdLen;
  327. char *tempString;
  328. char *argumentString;
  329. uint32_t argumentLength;
  330. uint32_t byteCounter;
  331. uint32_t idx;
  332. shellmatta_retCode_t ret = SHELLMATTA_OK;
  333. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  334. /** -# check parameters for plausibility */
  335. if( (NULL != inst)
  336. && (SHELLMATTA_MAGIC == inst->magic))
  337. {
  338. /** -# process byte wise */
  339. for (byteCounter = 0u; byteCounter < size; byteCounter++)
  340. {
  341. /*! -# set default values for the command argument - can be overwritten by heredoc */
  342. argumentString = inst->buffer;
  343. argumentLength = inst->inputCount;
  344. /** -# handle escape sequences */
  345. if(inst->escapeCounter != 0u)
  346. {
  347. escape_handleSequence(inst, *data);
  348. }
  349. /** -# ignore newline as first character (to be compatible to
  350. * terminals sending newline after return */
  351. else if((0u == inst->inputCount) && ('\n' == *data))
  352. {
  353. /* do nothing */
  354. }
  355. /** -# handle return as start of processing the command */
  356. else if ('\r' == *data)
  357. {
  358. if(0u == inst->hereLength)
  359. {
  360. /**
  361. * \dot
  362. * digraph heredocParser {
  363. * start -> wait [ label="<< in first line - store delimiter" ];
  364. * wait -> wait [ label="delimiter not detected" ];
  365. * wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
  366. * }
  367. * \enddot */
  368. /** -# check for heredoc - add string delimiter to stop strstr from searching too far */
  369. inst->buffer[inst->inputCount] = '\0';
  370. tempString = strstr(inst->buffer, "<<");
  371. if(NULL != tempString)
  372. {
  373. /*' -# check if length of heredoc delimiter is valid */
  374. if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
  375. {
  376. inst->hereStartIdx = (uint32_t)(tempString - inst->buffer);
  377. inst->hereDelimiterIdx = inst->hereStartIdx + 2u;
  378. while((inst->hereDelimiterIdx < inst->inputCount)
  379. && ( ('\0' == inst->buffer[inst->hereDelimiterIdx])
  380. || (' ' == inst->buffer[inst->hereDelimiterIdx])))
  381. {
  382. inst->hereDelimiterIdx ++;
  383. }
  384. inst->hereLength = inst->inputCount - inst->hereDelimiterIdx;
  385. inst->dirty = true;
  386. utils_insertChars(inst, data, 1);
  387. inst->lastNewlineIdx = inst->inputCount;
  388. }
  389. else
  390. {
  391. inst->hereLength = 0u;
  392. /** -# store the current command and reset the history buffer */
  393. inst->dirty = true;
  394. history_storeCmd(inst);
  395. history_reset(inst);
  396. }
  397. }
  398. else
  399. {
  400. /** -# store the current command and reset the history buffer */
  401. inst->dirty = true;
  402. history_storeCmd(inst);
  403. history_reset(inst);
  404. }
  405. }
  406. else
  407. {
  408. tempString = &inst->buffer[inst->lastNewlineIdx];
  409. cmdLen = inst->inputCount - inst->lastNewlineIdx;
  410. /** -# skip newline characters before comparison */
  411. while(('\n' == *tempString) || ('\r' == *tempString))
  412. {
  413. tempString ++;
  414. cmdLen --;
  415. }
  416. if( (inst->hereLength == cmdLen)
  417. && (0 == strncmp( &inst->buffer[inst->hereDelimiterIdx],
  418. tempString,
  419. inst->hereLength)))
  420. {
  421. argumentLength = inst->lastNewlineIdx;
  422. /** -# store the current command and reset the history buffer */
  423. inst->dirty = true;
  424. history_storeCmd(inst);
  425. history_reset(inst);
  426. /* TODO it is difficult to store the complete command in the history buffer if it is restructured before...
  427. * So this should be an extra function that can be called after parsing the command and before calling the command function */
  428. for(idx = 1u; idx <= inst->hereStartIdx; idx++)
  429. {
  430. inst->buffer[inst->hereDelimiterIdx + inst->hereLength - idx] = inst->buffer[inst->hereStartIdx - idx];
  431. }
  432. argumentString = &(inst->buffer[inst->hereDelimiterIdx + inst->hereLength - inst->hereStartIdx]);
  433. argumentLength = inst->lastNewlineIdx - ((inst->hereDelimiterIdx + inst->hereLength) - inst->hereStartIdx);
  434. inst->hereLength = 0u;
  435. }
  436. else
  437. {
  438. /*! -# the party goes on - print the \r and add a \n to satisfy most terminals */
  439. inst->lastNewlineIdx = inst->inputCount;
  440. utils_insertChars(inst, data, 1u);
  441. }
  442. }
  443. if(0u == inst->hereLength)
  444. {
  445. cmd = inst->cmdList;
  446. argumentString[argumentLength] = 0u;
  447. /** -# determine the cmd len (chars until first space or \0 is found */
  448. cmdLen = 0u;
  449. while( (cmdLen < argumentLength)
  450. && (' ' != argumentString[cmdLen])
  451. && ('\r' != argumentString[cmdLen])
  452. && ('\n' != argumentString[cmdLen])
  453. && ('\0' != argumentString[cmdLen]))
  454. {
  455. cmdLen ++;
  456. }
  457. /** -# search for a matching command */
  458. while (NULL != cmd)
  459. {
  460. /** -# compare command and alias string and length */
  461. if ( ((0 == strncmp( argumentString,
  462. cmd->cmd,
  463. cmdLen))
  464. && (cmdLen == strlen(cmd->cmd)))
  465. || ((NULL != cmd->cmdAlias)
  466. && ((0 == strncmp( argumentString,
  467. cmd->cmdAlias,
  468. cmdLen))
  469. && (cmdLen == strlen(cmd->cmdAlias)))))
  470. {
  471. utils_writeEcho(inst, "\r\n", 2u);
  472. cmdExecuted = 1u;
  473. cmd->cmdFct(inst, argumentString, argumentLength);
  474. cmd = NULL;
  475. }
  476. else
  477. {
  478. cmd = cmd->next;
  479. }
  480. }
  481. if ((cmdExecuted == 0u) && (inst->inputCount > 0))
  482. {
  483. inst->write("\r\nCommand: ", 11u);
  484. inst->write(argumentString, argumentLength);
  485. inst->write(" not found", 10u);
  486. }
  487. utils_terminateInput(inst);
  488. }
  489. }
  490. /** -# check for tabulator key - auto complete */
  491. else if('\t' == *data)
  492. {
  493. inst->dirty = true;
  494. autocomplete_run(inst);
  495. }
  496. /** -# check for cancel -
  497. * terminate current input and print prompt again */
  498. else if(3 == *data)
  499. {
  500. inst->dirty = false;
  501. history_reset(inst);
  502. utils_terminateInput(inst);
  503. }
  504. /** -# check for backspace */
  505. else if( ('\b' == *data)
  506. || ('\x7f' == *data))
  507. {
  508. inst->dirty = true;
  509. utils_removeChars(inst, 1u, true);
  510. }
  511. /** -# check for start of escape sequence */
  512. else if('\x1b' == *data)
  513. {
  514. inst->escapeCounter = 1u;
  515. }
  516. else
  517. {
  518. inst->dirty = true;
  519. utils_insertChars(inst, data, 1);
  520. }
  521. /** -# reset tab counter on not a tab */
  522. if ('\t' != *data)
  523. {
  524. inst->tabCounter = 0u;
  525. }
  526. data ++;
  527. }
  528. }
  529. else
  530. {
  531. ret = SHELLMATTA_USE_FAULT;
  532. }
  533. return ret;
  534. }
  535. /**
  536. * @brief simple write function to write a datastream to the output of an instance
  537. * @param[in] handle shellmatta instance handle
  538. * @param[in] data data to be written
  539. * @param[in] length amount of data to be written
  540. * @return
  541. */
  542. shellmatta_retCode_t shellmatta_write( shellmatta_handle_t handle,
  543. char *data,
  544. uint32_t length)
  545. {
  546. shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT;
  547. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  548. /** -# check parameters for plausibility */
  549. if( (NULL != inst)
  550. && (SHELLMATTA_MAGIC == inst->magic))
  551. {
  552. /** -# pass the data to the write function of the instance */
  553. ret = inst->write(data, length);
  554. }
  555. return ret;
  556. }
  557. #ifndef SHELLMATTA_STRIP_PRINTF
  558. /**
  559. * @brief printf like function to print output to the instances output
  560. * @param[in] handle shellmatta instance handle
  561. * @param[in] fmt format string and parameters
  562. * @return errorcode #SHELLMATTA_OK
  563. * #SHELLMATTA_USE_FAULT (no valid instance)
  564. * #SHELLMATTA_ERROR (buffer overflow or format err)
  565. */
  566. shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle,
  567. const char *fmt,
  568. ...)
  569. {
  570. char outputBuffer[SHELLMATTA_OUTPUT_BUFFER_SIZE];
  571. va_list arg;
  572. int length;
  573. shellmatta_retCode_t ret = SHELLMATTA_OK;
  574. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  575. /** -# check parameters for plausibility */
  576. if( (NULL != inst)
  577. && (SHELLMATTA_MAGIC == inst->magic))
  578. {
  579. /** -# assemble output string and write it */
  580. va_start(arg, fmt);
  581. length = vsnprintf(outputBuffer, SHELLMATTA_OUTPUT_BUFFER_SIZE, fmt, arg);
  582. va_end(arg);
  583. if(length < 0)
  584. {
  585. ret = SHELLMATTA_ERROR;
  586. }
  587. else
  588. {
  589. inst->write(outputBuffer, length);
  590. }
  591. }
  592. else
  593. {
  594. ret = SHELLMATTA_USE_FAULT;
  595. }
  596. return ret;
  597. }
  598. #endif
  599. /** @} */