Skip to content

File wsh_shell.c

File List > src > wsh_shell.c

Go to the documentation of this file

#include "wsh_shell.h"

static void WshShell_Stub_ExtClbk(void* pCtx) {
    (void)pCtx;
}

#define WSH_SHELL_USER_IS_AUTH()       (pShell->CurrUser != NULL)
#define WSH_SHELL_TMP_LOGIN_IS_EMPTY() (pShell->TmpAuth.Login[0] == 0)
#define WSH_SHELL_TMP_PASS_IS_EMPTY()  (pShell->TmpAuth.Pass[0] == 0)
#define WSH_SHELL_INTER_CMD_EXISTS()   (pShell->Interact.Handler != NULL)

static void WshShell_InvitationPrint(WshShell_t* pShell) {
    if (!WSH_SHELL_USER_IS_AUTH()) {
        if (WSH_SHELL_TMP_LOGIN_IS_EMPTY()) {
            WSH_SHELL_PRINT_SYS("Login: ");
        } else if (WSH_SHELL_TMP_PASS_IS_EMPTY()) {
            WSH_SHELL_PRINT_SYS("Password: ");
        }

        return;
    }

    WSH_SHELL_PRINT(pShell->PS1);
}

WSH_SHELL_RET_STATE_t WshShell_Init(WshShell_t* pShell, const WshShell_Char_t* pcDevName,
                                    const WshShell_Char_t* pcCustomHeader,
                                    WshShell_ExtCallbacks_t* pExtClbks) {
    WSH_SHELL_ASSERT(pShell && pcDevName);
    if (!pShell || !pcDevName)
        return WSH_SHELL_RET_STATE_ERR_PARAM;

    WSH_SHELL_MEMSET((void*)pShell, 0, sizeof(WshShell_t));

    WshShell_Size_t bufSize = sizeof(pShell->DeviceName);
    WshShell_Size_t len     = WSH_SHELL_STRLEN(pcDevName);
    if (len >= bufSize)
        len = bufSize - 1;

    WSH_SHELL_MEMCPY(pShell->DeviceName, pcDevName, len);
    pShell->DeviceName[len] = '\0';

    pShell->Version = WSH_SHELL_VERSION_STR;

    const WshShell_Size_t numClbks = sizeof(pShell->ExtCallbacks) / sizeof(WshShell_ExtClbk_t);
    WshShell_ExtClbk_t* pClbkArr   = (WshShell_ExtClbk_t*)&pShell->ExtCallbacks;

    if (!pExtClbks) {
        // Fill all with stub
        for (WshShell_Size_t clbk = 0; clbk < numClbks; clbk++) {
            pClbkArr[clbk] = WshShell_Stub_ExtClbk;
        }
    } else {
        // Fill from provided struct
        WshShell_ExtClbk_t* pInputArr = (WshShell_ExtClbk_t*)pExtClbks;
        for (WshShell_Size_t clbk = 0; clbk < numClbks; clbk++) {
            pClbkArr[clbk] = pInputArr[clbk] ? pInputArr[clbk] : WshShell_Stub_ExtClbk;
        }
    }

    const WshShell_Char_t* pBuildType = "release";
#if defined(WSH_SHELL_DEBUG_ENABLE)
    pBuildType = "debug";
#endif

    WSH_SHELL_PRINT("%c", WSH_SHELL_SYM_SOUND);
    WSH_SHELL_PRINT(WSH_SHELL_COLOR_PURPLE);
    WSH_SHELL_PRINT(pcCustomHeader ? pcCustomHeader : WSH_SHELL_HEADER);
    WSH_SHELL_PRINT_SYS("Serial shell service started on %s device\r\n", pShell->DeviceName);
    WSH_SHELL_PRINT_SYS("wsh-shell-v%s (%s), built in %s, at %s, with [%s], on [%s]\r\n",
                        pShell->Version, pBuildType, __DATE__, __TIME__, COMPILER, OS_NAME);
    (void)pBuildType;
    WSH_SHELL_PRINT_SYS(WSH_SHELL_PRESS_ENTER_TO_LOG_IN_STR "\r\n");

    WshShellPromptWait_Attach(&(pShell->PromptWait), WshShellPromptWait_Enter, NULL);

    return WSH_SHELL_RET_STATE_SUCCESS;
}

WshShell_Bool_t WshShell_Auth(WshShell_t* pShell, const WshShell_Char_t* pcLogin,
                              const WshShell_Char_t* pcPass) {
    WSH_SHELL_ASSERT(pShell && pcLogin && pcPass);
    if (!pShell || !pcLogin || !pcPass)
        return false;

    pShell->CurrUser = WshShellUser_FindByCredentials(&(pShell->Users), pcLogin, pcPass);
    if (WSH_SHELL_USER_IS_AUTH()) {
        WshShell_PS1Data_t data = {
            .UserName     = pShell->CurrUser->Login,
            .DevName      = pShell->DeviceName,
            .InterCmdName = WSH_SHELL_INTER_CMD_EXISTS() ? pShell->Interact.CmdName : NULL,
        };
        WshShell_GeneratePS1(pShell->PS1, &data);
        pShell->ExtCallbacks.Auth(NULL);
        WSH_SHELL_PRINT("%c", WSH_SHELL_SYM_SOUND);
    }

    return WSH_SHELL_USER_IS_AUTH();
}

WshShell_Bool_t WshShell_IsAuth(WshShell_t* pShell) {
    WSH_SHELL_ASSERT(pShell);
    if (!pShell)
        return false;

    return (bool)WSH_SHELL_USER_IS_AUTH();
}

void WshShell_DeAuth(WshShell_t* pShell, const WshShell_Char_t* pcReason) {
    WSH_SHELL_ASSERT(pShell && pcReason);
    if (!pShell || !pcReason)
        return;

    pShell->CurrUser = NULL;
    pShell->ExtCallbacks.DeAuth(NULL);
    WshShellHistory_Flush(&(pShell->HistoryIO));

    WSH_SHELL_PRINT("%c", WSH_SHELL_SYM_SOUND);
    WSH_SHELL_PRINT_ERR("Shell deAuthed by `%s`!\r\n", pcReason);
    WSH_SHELL_PRINT_SYS(WSH_SHELL_PRESS_ENTER_TO_LOG_IN_STR "\r\n");

    WshShellPromptWait_Attach(&(pShell->PromptWait), WshShellPromptWait_Enter, NULL);
}

static void WshShell_AuthHandler(WshShell_t* pShell) {
    WshShell_Size_t len = pShell->CommandLine.Len;

    if (WSH_SHELL_TMP_LOGIN_IS_EMPTY()) {
        if (len >= WSH_SHELL_LOGIN_LEN)
            len = WSH_SHELL_LOGIN_LEN - 1;

        WSH_SHELL_MEMCPY(pShell->TmpAuth.Login, pShell->CommandLine.Buff, len);
        pShell->TmpAuth.Login[len] = '\0';
    } else if (WSH_SHELL_TMP_PASS_IS_EMPTY()) {
        if (len >= WSH_SHELL_PASS_LEN)
            len = WSH_SHELL_PASS_LEN - 1;

        WSH_SHELL_MEMCPY(pShell->TmpAuth.Pass, pShell->CommandLine.Buff, len);
        pShell->TmpAuth.Pass[len] = '\0';
    }

    if (!WSH_SHELL_TMP_LOGIN_IS_EMPTY() && !WSH_SHELL_TMP_PASS_IS_EMPTY()) {
        WshShell_Bool_t isAuthOk =
            WshShell_Auth(pShell, pShell->TmpAuth.Login, pShell->TmpAuth.Pass);

        WSH_SHELL_MEMSET((void*)pShell->TmpAuth.Login, 0, WSH_SHELL_LOGIN_LEN);
        WSH_SHELL_MEMSET((void*)pShell->TmpAuth.Pass, 0, WSH_SHELL_PASS_LEN);

        if (!isAuthOk)
            WSH_SHELL_PRINT("%c", WSH_SHELL_SYM_SOUND);
    }

    WshShellIO_ClearInterBuff(&(pShell->CommandLine));
}

static void WshShell_StringHandler(WshShell_t* pShell) {
    pShell->CommandLine.Buff[pShell->CommandLine.Len] = 0;

    const WshShell_Char_t* pcCmdStr =
        WshShellStr_TrimString(pShell->CommandLine.Buff, pShell->CommandLine.Len);
    WshShell_Char_t cmdStr[WSH_SHELL_INTR_BUFF_LEN] = {0};
    WSH_SHELL_STRNCPY(cmdStr, pcCmdStr, WSH_SHELL_INTR_BUFF_LEN - 1);

    WshShell_Size_t argc                                      = 0;
    const WshShell_Char_t* pсArgv[WSH_SHELL_CMD_ARGS_MAX_NUM] = {0};

    WshShellStr_ParseToArgcArgv(cmdStr, &argc, pсArgv, WSH_SHELL_CMD_ARGS_MAX_NUM);
    if (argc == 0)
        return;

    WshShellHistory_SaveCmd(&(pShell->HistoryIO), pcCmdStr, WSH_SHELL_STRLEN(pcCmdStr));

    const WshShellCmd_t* pcCmd      = WshShellDefCmd_GetPtr();
    WshShellCmdHandler_t cmdHandler = NULL;

    if (WSH_SHELL_STRNCMP(pcCmd->Name, pсArgv[0], WSH_SHELL_CMD_NAME_LEN) == 0) {
        cmdHandler = pcCmd->Handler;
    } else {
        pcCmd = WshShellCmd_SearchCmd(&(pShell->Commands), pсArgv[0]);
        if (pcCmd == NULL) {
            WSH_SHELL_PRINT_WARN("Command \"%s\" not found!\r\n", pcCmdStr);
        } else if ((pShell->CurrUser->Groups & pcCmd->Groups) != 0) {
            cmdHandler = pcCmd->Handler;
        } else {
            WSH_SHELL_PRINT_ERR(
                "Access denied: no group intersection for command \"%s\" and user \"%s\"!\r\n",
                pсArgv[0], pShell->CurrUser->Login);
            return;
        }
    }

    if (cmdHandler) {
        WSH_SHELL_RET_STATE_t retState = cmdHandler(pcCmd, argc, pсArgv, pShell);

        if (retState != WSH_SHELL_RET_STATE_SUCCESS) {
            WSH_SHELL_PRINT_ERR("Command execution: %s\r\n", WshShell_GetRetStateStr(retState));
        } else {
            if (WSH_SHELL_INTER_CMD_EXISTS()) {
                WshShell_PS1Data_t data = {
                    .UserName     = pShell->CurrUser->Login,
                    .DevName      = pShell->DeviceName,
                    .InterCmdName = pShell->Interact.CmdName,
                };
                WshShell_GeneratePS1(pShell->PS1, &data);
            }
        }
    }

    WshShellIO_ClearInterBuff(&(pShell->CommandLine));
}

static void WshShell_StringInteractHandler(WshShell_t* pShell) {
    pShell->CommandLine.Buff[pShell->CommandLine.Len] = 0;
    const WshShell_Char_t* pcCmdStr                   = pShell->CommandLine.Buff;

    WshShellHistory_SaveCmd(&(pShell->HistoryIO), pcCmdStr, WSH_SHELL_STRLEN(pcCmdStr));
    pShell->Interact.Handler(&(pShell->CommandLine));
    WshShellIO_ClearInterBuff(&(pShell->CommandLine));
}

static void WshShell_SymbolHandler(WshShell_t* pShell, const WshShell_Char_t symbol) {
    switch (symbol) {
        case WSH_SHELL_SYM_EXIT:
            if (WSH_SHELL_INTER_CMD_EXISTS()) {
                WshShellInteract_Flush(&(pShell->Interact));

                WshShell_PS1Data_t data = {
                    .UserName     = pShell->CurrUser->Login,
                    .DevName      = pShell->DeviceName,
                    .InterCmdName = NULL,
                };
                WshShell_GeneratePS1(pShell->PS1, &data);
                WSH_SHELL_PRINT(WSH_SHELL_END_LINE);
                WshShell_InvitationPrint(pShell);
            } else
                WshShell_DeAuth(pShell, "Ctrl+D");
            break;

        case WSH_SHELL_SYM_BACKSPACE:
        case WSH_SHELL_SYM_DELETE:
            WshShellIO_RemoveLeftSymbol(&(pShell->CommandLine));
            break;

        case WSH_SHELL_SYM_TAB:
            if (!WSH_SHELL_USER_IS_AUTH())
                break;

            if (WshShellAutocomplete_Try(pShell->CommandLine.Buff, pShell->CommandLine.Len,
                                         &(pShell->Commands))) {
                WshShellIO_RefreshConsoleFromInterBuff(&(pShell->CommandLine));
            } else {
                WshShell_InvitationPrint(pShell);
                WshShellIO_PrintInterBuff(&(pShell->CommandLine));
            }
            break;

        case WSH_SHELL_ESC_SEQ_START_CHAR:
            WshShellEsc_StartSeq(&(pShell->EscStorage));
            break;

        default:
            if (!WshShellStr_IsPrintableAscii(symbol)) {
                WSH_SHELL_PRINT("%c", WSH_SHELL_SYM_SOUND);
                return;
            }

            WshShell_Bool_t starsOrChars =
                (bool)(!WSH_SHELL_USER_IS_AUTH() && !WSH_SHELL_TMP_LOGIN_IS_EMPTY());
            WshShellIO_InsertSymbol(&(pShell->CommandLine), symbol, starsOrChars);
            break;
    }
}

#define SHELL_SAVE_PREV_AND_RETURN(pShell, sym) \
    do {                                        \
        (pShell)->PrevSym = (sym);              \
        return;                                 \
    } while (0)

void WshShell_InsertChar(WshShell_t* pShell, const WshShell_Char_t symbol) {
    WSH_SHELL_ASSERT(pShell);
    if (!pShell)
        return;

    pShell->ExtCallbacks.SymbolIn(NULL);

    WshShell_Bool_t isEnterPressed = false;
    if (symbol == WSH_SHELL_CHAR_CR || symbol == WSH_SHELL_CHAR_LF) {
        if (pShell->PrevSym == WSH_SHELL_CHAR_CR && symbol == WSH_SHELL_CHAR_LF) {
            SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
        }

        isEnterPressed = true;
    }

    WSH_SHELL_RET_STATE_t promptWaitRes = WshShellPromptWait_Handle(&(pShell->PromptWait), symbol);
    if (promptWaitRes == WSH_SHELL_RET_STATE_ERR_BUSY) {
        SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
    }

    if (isEnterPressed) {
        WSH_SHELL_PRINT(WSH_SHELL_END_LINE);

        if (!WSH_SHELL_USER_IS_AUTH()) {
            WshShell_AuthHandler(pShell);
            WshShell_InvitationPrint(pShell);
            SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
        }

        if (WSH_SHELL_INTER_CMD_EXISTS()) {
            WshShell_StringInteractHandler(pShell);
        } else
            WshShell_StringHandler(pShell);

        if (WshShell_IsAuth(pShell))
            WshShell_InvitationPrint(pShell);

        SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
    }

    if (WshShellEsc_IsSeqStarted(&(pShell->EscStorage))) {
        WshShellEsc_Handler(&(pShell->HistoryIO), &(pShell->CommandLine), &(pShell->EscStorage),
                            symbol);
        SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
    }

    WshShell_SymbolHandler(pShell, symbol);
    SHELL_SAVE_PREV_AND_RETURN(pShell, symbol);
}