Simple embedded shell with runtime pluggable commands.

Dependents:   DataBus2018

Implements a simple unix-like shell for embedded systems with a pluggable command architecture.

SimpleShell.cpp

Committer:
shimniok
Date:
2018-12-31
Revision:
35:1a8c5fce8895
Parent:
34:afe994ca0e49

File content as of revision 35:1a8c5fce8895:

#include "SimpleShell.h"
#include "ff.h"
#include <ctype.h>
#include <string>
#include <list>

#define ESC     0x1b
#define UP      0x41
#define DOWN    0x42
#define RIGHT   0x43
#define LEFT    0x44
#define HOME    0x31
#define INS     0x32
#define DEL     0x33
#define PGUP    0x35
#define PGDN    0x36
#define TAIL    0x7e


SimpleShell::SimpleShell(char *home)
{
    lookupEnd = 0;

    // Set home directory
    strncpy(_home, canon(home), MAXBUF);

    // Set current working directory
    strncpy(_cwd, _home, MAXBUF);

    // Built-in shell commands
    command(callback(this, &SimpleShell::help), "help");
    command(callback(this, &SimpleShell::pwd), "pwd");
    command(callback(this, &SimpleShell::cat), "cat");
    command(callback(this, &SimpleShell::cd), "cd");
    command(callback(this, &SimpleShell::rm), "rm");
    command(callback(this, &SimpleShell::touch), "touch");
    command(callback(this, &SimpleShell::ls), "ls");
    command(callback(this, &SimpleShell::send), "send");
}


bool SimpleShell::haswildcard(char *s) {
    bool result = strchr(s, '*') != NULL;
    return result;
}


/// Path stack representation
typedef std::list<char*> path_t;

char *SimpleShell::canon(char *path) {
    path_t pstack;
    static char tmp[MAXBUF*2];
    char *e;

    // if we're passed empty/null string, just send back cwd so nothing breaks
    if (path == NULL || strlen(path) == 0) {
        strcpy(tmp, _cwd);
        return tmp;
    }

    // relative path? add current working directory to path stack
    if (path[0] != '/') {
        strcpy(tmp, _cwd);
        strcat(tmp, "/");
        strcat(tmp, path);
    } else {
        strcpy(tmp, path);
    }

    // now canonicalize the path spec
    e = strtok(tmp+1, "/");
    while (e) {
        //printf("e = <%s>\n", e);
        if (strcmp("..", e) == 0) {
            // pop most recent directory
            if (!pstack.empty())
                pstack.pop_back();
        } else if (strcmp(".", e) != 0) {
            // push this dir onto path
            if (strlen(e) > 0)
                pstack.push_back(e);
        }
        e = strtok(NULL, "/");
    }

    static std::string result;
    result = "";

    for (path_t::iterator it = pstack.begin(); it != pstack.end(); it++) {
      result.append("/");
      result.append(*it);
    }

    // if empty, add a /
    if (result.size() < 2) {
      result.append("/");
    }

    strcpy(tmp, result.c_str());

    return tmp;
}


char *SimpleShell::basename(char *path) {
    char *result = strrchr(path, '/');
    
    if (result == NULL) {
        result = path;
    } else {
        if (result[0] == '/') result++;
    }
    
    return result;
}


void SimpleShell::help(int argc, char **argv)
{
    printf("Available commands: ");
    for (int i=0; i < lookupEnd; i++) {
        printf("%s ", lookup[i].command);
    }
    printf("\n\n");
}


void SimpleShell::cd(int argc, char **argv)
{
    if (argc == 1) {
        strncpy(_cwd, _home, MAXBUF);
    } else if (argc == 2) {
        strncpy(_cwd, canon(argv[1]), MAXBUF);
    } else {
        puts("usage: cd directory");
    }
    return;
}


void SimpleShell::pwd(int argc, char **argv)
{
    puts(_cwd);
    return;
}


void SimpleShell::ls(int argc, char **argv)
{
    DIR *d;
    struct dirent *p;
    char *path;

    if (argc == 1) {
        path = _cwd;
    } else if (argc == 2) {
        path = canon(argv[1]);
    } else {
        puts("usage: ls [directory]");
        return;
    }

    int cols=0;
    if ((d = opendir(path)) != NULL) {
        while ((p = readdir(d)) != NULL) {
            if (p->d_name && p->d_name[0] != 0xff) {
                if (cols++ > 3) {
                    putc('\n', stdout);
                    cols = 0;
                }
                printf("%-15s  ", p->d_name);
            }
        }
        putc('\n', stdout);
        if (cols < 3)
            putc('\n', stdout);
        closedir(d);
    } else {
        puts(path);
        puts(": No such directory\n");
    }

    return;
}


char *SimpleShell::dirname(char *path)
{
    char *found = strrchr(path, '/');
    char *result = new char[sizeof(path)];

    char *s = result;
    char *t = path;
    while (t < found) {
        *s++ = *t++;
    }
    *s = 0;

    return result;    
}    


/** Attempts to match filename pattern and name.
 * @param pattern is a pattern containing one '*'
 * @param name is the name to check for a match to the pattern
 * @returns 1 if match found, 0 if no match or bad pattern
 */ 
int fnmatch(char *pattern, char *name)
{
    char *p;
    char *n;
    int c;

    // only one * allowed
    p = pattern;
    c = 0;
    p = strchr(p, '*');
    while (p) {
        c++;
        p = strchr(p+1, '*');
    }
    if (c != 1) return 0;

    // match first part
    //puts("first");
    p = pattern;
    n = name;
    char *s = strchr(pattern, '*');
    while (p != s) {
        // mismatch before *?
        if (toupper(*p) != toupper(*n)) {
            return 0;
        }
        p++;
        n++;
    }
    // match last part in reverse
    //puts("second");
    p = strchr(pattern, 0)-1;
    n = strchr(name, 0)-1;
    while (p != s) {
        // mismatch before *?
        //printf("%c %c\n", *p, *n);
        if (toupper(*p) != toupper(*n)) {
            return 0;
        }
        p--;
        n--;
    }

    return 1;
}


// Run the specified callback on each matching filename
char *SimpleShell::foreach(char *pattern)
{
    DIR *d = 0;
    char *base;
    char *path;
    struct dirent *p;

    base = basename(pattern);
    path = dirname(pattern);
    printf("dir:<%s> base:<%s>\n", path, base);
    if ((d = opendir(path)) != NULL) {
        while ((p = readdir(d)) != NULL) {
            //printf("pattern:<%s> file:<%s>\n", base, p->d_name);
            if (fnmatch(base, p->d_name) == 1) {
                char *next = new char[sizeof(base) + sizeof(p->d_name) + 2];
                sprintf(next, "%s/%s", path, p->d_name);
                printf("Removing %s...\n", next);
                int stat = remove(next);
                //int stat = f_unlink(next);
                if (stat) {
                    printf("%s: could not remove. Error %d\n", next, stat);
                }
                delete[] next;
            }//if
        }//while
        closedir(d);
    } else {
        printf("%s: no such directory\n", path);
    }
       
    return 0;
}


void SimpleShell::rm(int argc, char **argv)
{
    char *arg;

    if (argc >= 2) {
        for (int i=1; i < argc; i++) {
            arg = canon(argv[i]);
            if (haswildcard(argv[i])) {
                foreach(arg);
            } else if (remove(canon(argv[i]))) {
                printf("%s: cannot remove. Error %d\n", argv[i], errno);
            }
        }
    } else {
        puts("usage: rm [file1 [file2 ...]]");
    }
}


void SimpleShell::touch(int argc, char **argv)
{
    FILE *fp;

    if (argc >= 2) {
        for (int i=1; i < argc; i++) {
            if ((fp = fopen(canon(argv[i]), "w")) != NULL) {
                fclose(fp);
            } else {
                printf("%s: cannot touch\n", argv[1]);
            }
        }
    } else {
        puts("usage: touch [file1 [file2 ...]]");
    }
}


void SimpleShell::cat(int argc, char **argv)
{
    FILE *fp;
    //int status=0;
    char *buf = new char[MAXBUF];

    for (int i=1; i < argc; i++) {
        //resolveDirectory(path, arg);
        if ((fp = fopen(canon(argv[i]), "r")) != NULL) {
            while (!feof(fp)) {
                fgets(buf, MAXBUF-1, fp);
                fputs(buf, stdout);
            }
            fclose(fp);
        } else {
            fputs(argv[i], stdout);
            fputs(": No such file\n", stdout);
            //status = 1;
        }
    }
    delete[] buf;

    return;
}


void SimpleShell::send(int argc, char **argv)
{
    const char SOF=0x01;
    const char ETX=0x03;
    const char EOT=0x04;
    const char ACK=0x07;
    const char NACK=0x18;
    char buf[1024];
    FILE *fp;
    char c;

    int i = 1;
    if (argc >= 2) {
        if ((fp = fopen(canon(argv[i]), "r")) == 0) {
            printf("%s: can't open file\n", basename(argv[i]));
        } else {
            puts("Ready; initiate receive on client.");
            c = getchar();
            if (c == SOF) {
                puts(basename(argv[i]));
                c = getchar();
                if (c == ACK) {
                    while (!feof(fp)) {
                        fgets(buf, 1024, fp);
                        fputs(buf, stdout);
                    }
                    putchar(EOT);
                } else if (c == NACK) {
                    puts("client error.");
                } else {
                    puts("ACK/NACK expected");
                }
            } else if (c == NACK) {
                puts("server cancelled.");
            } else {
                puts("SOF/NACK expected.");
            }
            fclose(fp);
        }
    } else {
        puts("usage: send file1 [file2 ...]");
    }
}


void SimpleShell::run()
{
    bool done=false;
    callback_t cb;
    //int status; // TODO implement command status return
    std::string x;

    printf("Type help for assistance.\n");
    help(0, NULL);
    while (!done) {
        printPrompt();
        readCommand();
        if (argv[0]) { // skip blank command
            if (cb = findCommand()) {
                cb.call(argc, argv);
            } else {
                printf("command <%s> not found\n", argv[0]);
            }
        }
    }
    puts("exiting shell\n");

    return;
}


void SimpleShell::command(callback_t cb, char *command)
{
    if (lookupEnd < MAXLOOKUP) {
        lookup[lookupEnd].cb = cb;
        lookup[lookupEnd].command = command;
        lookupEnd++;
    }

    return;
}


SimpleShell::callback_t SimpleShell::findCommand()
{
    SimpleShell::callback_t cb=NULL;

    for (int i=0; i < lookupEnd; i++) {
        if (strncmp(argv[0], lookup[i].command, MAXBUF) == 0) {
            cb = lookup[i].cb;
            break;
        }
    }

    return cb;
}


void SimpleShell::printPrompt()
{
    fputc('(', stdout);
    fputs(_cwd, stdout);
    fputs(")# ", stdout);

    return;
}


void SimpleShell::readCommand()
{
    int i=0;
    char c;
    bool done = false;
    static char cmd[MAXBUF];

    memset(cmd, 0, MAXBUF);
    do {
        cmd[i] = 0;
        c = fgetc(stdin);
        if (c == '\r') { // if return is hit, we're done, don't add \r to cmd
            done = true;
        } else if (c == ESC) { // keyboard escape codes (arrow keys, etc)
            int c2 = getchar();
            int c3 = getchar();

            if (c2 == 0x4f && c3 == 0x46) {
                printf("<END>");
            } else if (c2 == 0x5b) {

                if (c3 == UP) {
                    printf("<UP>");
                } else if (c3 == DOWN) {
                    printf("<DOWN>");
                } else if (c3 == RIGHT) {
                    printf("<RIGHT>");
                } else if (c3 == LEFT) {
                    printf("<LEFT>");
                } else if (c3 == HOME || c3 == INS || c3 == DEL ||
                           c3 == PGUP || c3 == PGDN) {
                    char c4 = getchar();
                    if (c4 == TAIL) {
                        if (c4 == HOME) {
                            printf("<HOME>");
                        } else if (c4 == INS) {
                            printf("<INS>");
                        } else if (c4 == DEL) {
                            printf("<DEL>");
                        } else if (c4 == PGUP) {
                            printf("<PGUP>");
                        } else if (c4 == PGDN) {
                            printf("<PGDN>");
                        }//if
                    }//if
                }//if
            }//if
            //printf("\n");
        } else if (i < MAXBUF-1) {
            if (c == 0x7f || c == '\b') { // backspace or delete
                if (i > 0) { // if we're at the beginning, do nothing
                    i--;
                    fputs("\b \b", stdout);
                }
            } else {
                if (isprint(c))
                    fputc(c, stdout);
                cmd[i++] = c;
            }
        }

    } while (!done);
    fputc('\n', stdout);

    // remove leading/trailing whitespace
    char *s = cmd;
    while (isspace(*s)) {
        s++;
    }
    for (int j=i; j >= 0 && isspace(cmd[j]); j--) {
        cmd[j] = '\0';
    }

    // split into command and arguments
    argc = 0;
    char *t;

    for (int i=0; i < MAXARGS; i++) {
        argv[i] = NULL;
    }
    t = strtok(s, " ");
    while (t && argc < MAXARGS) {
        argv[argc++] = t;
        t = strtok(NULL, " ");
    }

    return;
}