Simple embedded shell with runtime pluggable commands.
Implements a simple unix-like shell for embedded systems with a pluggable command architecture.
SimpleShell.cpp
- Committer:
- shimniok
- Date:
- 2018-12-28
- Revision:
- 34:afe994ca0e49
- Parent:
- 33:84dc443909a0
- Child:
- 35:1a8c5fce8895
File content as of revision 34:afe994ca0e49:
#include "SimpleShell.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 || strchr(s, '?') != NULL || (strchr(s, '[') != NULL && 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; } typedef struct { char *dir; char *base; } filespec_t; char *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; } #include "fnmatch.h" list<char*> *scandir(char *pattern, char *path) { DIR *d; list<char*> *listp; struct dirent *p; if ((d = opendir(path)) != NULL) { listp = new list<char*>; while ((p = readdir(d)) != NULL) { if (fnmatch(pattern, p->d_name, FNM_PATHNAME|FNM_CASEFOLD) != FNM_NOMATCH) { char *s = new char[sizeof(p->d_name)]; strcpy(s, p->d_name); listp->push_back(s); } } closedir(d); } return listp; } void SimpleShell::rm(int argc, char **argv) { if (argc >= 2) { for (int i=1; i < argc; i++) { char *arg = canon(argv[i]); char *base = basename(arg); char *dir = dirname(arg); printf("arg=<%s>\nbase=<%s>\ndir=<%s>\n", arg, base, dir); // wildcards only valid in basename for now if (haswildcard(base)) { list<char*> *fl = scandir(base, dir); char *s; while (!fl->empty()) { s = fl->front(); printf("<%s>\n", s); fl->pop_front(); delete[] s; } delete fl; } else { if (remove(canon(argv[i]))) { printf("%s: cannot remove\n", argv[i]); } } delete[] dir; } } 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; }