greenscsi/src/cmd.ino

2080 lines
61 KiB
C++
Executable File

#include "cmd.h"
// command line message buffer and pointer
static uint8_t msg[MAX_MSG_SIZE] = "";
static uint8_t *msg_ptr = msg;
int echo_on = 1;
int errorlevel = 0;
// Exec script stack, this allows executing scripts from within scripts.
typedef struct script_s {
char m_filename[MAX_FILE_PATH+1];
FsFile m_file;
int m_line;
} script_t;
#define MAX_SCRIPTLEVEL 8
script_t scriptstack[MAX_SCRIPTLEVEL];
int scriptlevel = -1;
#define exec_filename scriptstack[scriptlevel].m_filename
#define exec_line scriptstack[scriptlevel].m_line
char cmd_prefix[256] = "/";
extern uint8_t mbr_bin[];
uint8_t zero[512];
void cmdDisplay() {
if(cmd_prefix[0] != 0) {
Serial.printf("[%s] $ ", cmd_prefix);
} else {
strcpy(cmd_prefix, "/");
Serial.print("[/] $ ");
}
}
/*
* Like strtok, but with quotes
*/
char *strmbtok ( char *input, const char *delimit, const char *openblock, const char *closeblock) {
static char *token = NULL;
char *lead = NULL;
char *block = NULL;
int iBlock = 0;
int iBlockIndex = 0;
if ( input != NULL) {
token = input;
lead = input;
}
else {
lead = token;
if ( *token == '\0') {
lead = NULL;
}
}
while ( *token != '\0') {
// We are in a quote, is this a matching close quote character?
if ( iBlock) {
if ( closeblock[iBlockIndex] == *token) {
iBlock = 0;
}
token++;
continue;
}
// Is this a valid opening quote?
if ( ( block = strchr ( openblock, *token)) != NULL) {
iBlock = 1;
iBlockIndex = block - openblock;
token++;
continue;
}
if ( strchr ( delimit, *token) != NULL) {
*token = '\0';
token++;
break;
}
token++;
}
return lead;
}
/*
* Remove quotes from quoted strings.
*/
void unquote(char *out, char *in) {
char tmp_str[256];
if(!in) return;
if(!out) return;
char *pout = tmp_str;
char inQuote = 0;
while(*in != 0) {
if(inQuote && (*in == inQuote)) {
inQuote = 0;
} else if(!inQuote && (*in == '"')) {
inQuote = '"';
} else if(!inQuote && (*in == '\'')) {
inQuote = '\'';
} else {
*pout = *in;
pout++;
}
in++;
}
*pout = 0;
// This way you can have out == in
strcpy(out, tmp_str);
}
#define ENVIRONMENT_SIZE 32
#define ENVIRONMENT_KEY_SIZE 32
#define ENVIRONMENT_VALUE_SIZE (MAX_FILE_PATH+1)
typedef struct cmdvar_s {
char key[ENVIRONMENT_KEY_SIZE];
char value[ENVIRONMENT_VALUE_SIZE];
} cmdvar_t;
cmdvar_t cmdenv[ENVIRONMENT_SIZE];
int getvar(const char *varname, int defaultvalue) {
if(!strcasecmp(varname, "ERRORLEVEL")) return errorlevel;
if(!strcasecmp(varname, "DEBUG")) return debuglog;
for(int i = 0; i < ENVIRONMENT_SIZE; i++) {
if(!strcmp(cmdenv[i].key, varname)) {
return strtol(cmdenv[i].value, NULL, 0);
}
}
return defaultvalue;
}
char* getvar(const char *varname, char *defaultvalue) {
if(!strcasecmp(varname, "ERRORLEVEL")) return NULL;
if(!strcasecmp(varname, "DEBUG")) return NULL;
for(int i = 0; i < ENVIRONMENT_SIZE; i++) {
if(!strcasecmp(cmdenv[i].key, varname)) {
return cmdenv[i].value;
}
}
return defaultvalue;
}
void setvarcmd(int argc, char **argv) {
int i;
if(argc != 3) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Syntax error in setvar command.\r\n");
return;
}
if(!strcasecmp(argv[1], "ERRORLEVEL")) return;
if(!strcasecmp(argv[1], "DEBUG")) { debuglog = strtoul(argv[2], NULL, 0); return; }
// Find existing variable matching name and replace it
for(i = 0; i < ENVIRONMENT_SIZE; i++) {
if(!strcasecmp(cmdenv[i].key, argv[1])) {
strncpy(cmdenv[i].value, argv[2], ENVIRONMENT_VALUE_SIZE);
cmdenv[i].value[ENVIRONMENT_VALUE_SIZE-1] = 0;
return;
}
}
// Create new variable if we have room.
for(i = 0; i < ENVIRONMENT_SIZE; i++) {
if(cmdenv[i].key[0] == 0) {
strncpy(cmdenv[i].key, argv[1], ENVIRONMENT_KEY_SIZE);
cmdenv[i].key[ENVIRONMENT_KEY_SIZE-1] = 0;
strncpy(cmdenv[i].value, argv[2], ENVIRONMENT_VALUE_SIZE);
cmdenv[i].value[ENVIRONMENT_VALUE_SIZE-1] = 0;
return;
}
}
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Ran out of environment variable space.\r\n");
}
void unsetvarcmd(int argc, char **argv) {
int i;
if(argc != 2) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Syntax error in unsetvar command.\r\n");
return;
}
if(!strcasecmp(argv[1], "ERRORLEVEL")) return;
if(!strcasecmp(argv[1], "DEBUG")) { debuglog = 0; return; }
// Find existing variable matching name and delete it
for(i = 0; i < ENVIRONMENT_SIZE; i++) {
if(!strcasecmp(cmdenv[i].key, argv[1])) {
memset(cmdenv[i].key, 0, ENVIRONMENT_KEY_SIZE);
memset(cmdenv[i].value, 0, ENVIRONMENT_VALUE_SIZE);
return;
}
}
// Quietly fail if the variable doesn't exist
}
void getvarcmd(int argc, char **argv) {
int i;
if(argc > 2) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Syntax error in getvar command.\r\n");
return;
}
if(!strcasecmp(argv[1], "ERRORLEVEL")) {
Serial.printf("%d\r\n", errorlevel);
return;
}
if(!strcasecmp(argv[1], "DEBUG")) {
Serial.printf("%d\r\n", debuglog);
return;
}
for(i = 0; i < ENVIRONMENT_SIZE; i++) {
if(!strcasecmp(cmdenv[i].key, argv[1])) {
Serial.printf("%s=%s\r\n", cmdenv[i].key, cmdenv[i].value);
return;
}
}
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": variable '%s' not found.\r\n", argv[1]);
return;
}
void cmdParse(char *cmd) {
uint8_t argc, i = 0;
char *argv[MAXARG];
// leading whitespace and blank lines are ignored
while((*cmd == ' ') || (*cmd == '\t')) cmd++;
if(!cmd[0]) return;
// ignore labels for regular command parsing
if(cmd[0] == ':') return;
// parse the statement and tokenize it (with quote handling)
argv[i] = strmbtok(cmd, " ", "\"'", "\"'");
while ((argv[i] != NULL) && (i < MAXARG)) {
// unquote the previous argument (last argument is always NULL)
unquote(argv[i], argv[i]);
argv[++i] = strmbtok(NULL, " ", "\"'", "\"'");
};
// save the number of arguments
argc = i;
while(!strcasecmp(argv[0], "if")) {
if(argc > 3) {
int aval = getvar(argv[1], 0);
int cval = strtol(argv[3], NULL, 0);
if(!strcmp(argv[2], ">")) {
if(!(aval > cval)) return;
} else if(!strcmp(argv[2], "<")) {
if(!(aval < cval)) return;
} else if(!strcmp(argv[2], ">=")) {
if(!(aval >= cval)) return;
} else if(!strcmp(argv[2], "<=")) {
if(!(aval <= cval)) return;
} else if(!strcmp(argv[2], "==")) {
if(!(aval == cval)) return;
} else if(!strcmp(argv[2], "!=")) {
if(!(aval != cval)) return;
} else {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unknown if conditional '%s'.\r\n", argv[1]);
return;
}
// condition met, parse rest of the line, skipping over "then" if present.
if(!strcmp(argv[3], "then")) {
argc -= 4;
for(int iz=0; iz < argc; iz++) {
argv[iz] = argv[iz+4];
}
} else {
argc -= 3;
for(int iz=0; iz < argc; iz++) {
argv[iz] = argv[iz+3];
}
}
} else {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Not enough parameters to if command.\r\n");
return;
}
}
// find the handler responsible for this command
for(int ii=0; GlobalCommands[ii].Name; ii++) {
if (!strcmp(argv[0], GlobalCommands[ii].Name)) {
errorlevel = 0;
GlobalCommands[ii].Func(argc, argv);
return;
}
}
// command not recognized. print message and re-generate prompt.
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unknown command '%s'.\r\n", argv[0]);
}
void cmd_handler() {
char c = Serial.read();
switch (c) {
case '\n':
break;
case '\r':
// terminate the msg and reset the msg ptr. then send
// it to the handler for processing.
*msg_ptr = '\0';
if(msg[0] != 0) {
Serial.print("\r\n");
cmdParse((char *)msg);
execLoop();
cmdDisplay();
} else {
Serial.print("\r\n");
cmdDisplay();
}
msg_ptr = msg;
break;
case '\b':
// backspace
if (msg_ptr > msg)
{
*msg_ptr = '\0';
Serial.print(c);
msg_ptr--;
} else {
Serial.print('\x07');
msg_ptr = msg;
msg[0] = 0;
}
break;
default:
// normal character entered. add it to the buffer
if(msg_ptr < (msg + sizeof(msg) - 1)) {
Serial.print(c);
*msg_ptr++ = c;
*msg_ptr = '\0';
} else {
Serial.print('\x07');
}
break;
}
}
void cmdPoll() {
if (Serial.available()) {
cmd_handler();
}
}
void gotocmd(int argc, char **argv) {
fspos_t tmp_pos;
int tmp_line = 1;
char tmp_cmd[256];
if(scriptlevel < 0) {
errorlevel = -1;
Serial.printf("ERROR: goto is only valid inside a script.\r\n");
return;
}
// Save our current position in case we can't find the label (should we exit the script on goto failure?)
scriptstack[scriptlevel].m_file.fgetpos(&tmp_pos);
// Rewind to the top of the script in case the label is up instead of down.
scriptstack[scriptlevel].m_file.rewind();
// Find the label
while(scriptstack[scriptlevel].m_file.available()) {
int n = scriptstack[scriptlevel].m_file.fgets(tmp_cmd, sizeof(tmp_cmd)-1, NULL);
if (n <= 0) {
Serial.printf("Reading script '%s' failed at line %d.\r\n", exec_filename, exec_line);
break;
}
if(tmp_cmd[0] == ':') {
if(!strcmp(tmp_cmd+1, argv[1])) {
// Found it, script continues on the next line after the label. Update the line counter so any errors show the correct line reference.
exec_line = tmp_line;
return;
}
}
tmp_line++;
}
// We couldn't find the label, restore the file position.
scriptstack[scriptlevel].m_file.fsetpos(&tmp_pos);
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Label '%s' was not found.\r\n", argv[1]);
}
void exitcmd(int argc, char **argv) {
if(scriptlevel < 0) {
errorlevel = -1;
Serial.printf("ERROR: exit is only valid inside a script.\r\n");
return;
}
scriptstack[scriptlevel].m_file.close();
exec_line = 0;
scriptlevel--;
}
int execLoop() {
char exec_cmd[256];
while(scriptlevel >= 0) {
if(scriptstack[scriptlevel].m_file.available()) {
int n = scriptstack[scriptlevel].m_file.fgets(exec_cmd, sizeof(exec_cmd)-1, NULL);
if (n <= 0) {
Serial.printf("Reading script '%s' failed at line %d.\r\n", exec_filename, exec_line);
goto close_file;
}
exec_cmd[sizeof(exec_cmd)-1] = 0;
for(int ii = 0; ii < n; ii++) {
if(exec_cmd[ii] == '\r') exec_cmd[ii] = 0;
if(exec_cmd[ii] == '\n') exec_cmd[ii] = 0;
}
if(echo_on && exec_cmd[0]!='@') {
cmdDisplay();
Serial.printf("%s\r\n", exec_cmd);
}
if(exec_cmd[0] != '#')
cmdParse((exec_cmd[0]!='@') ? exec_cmd : exec_cmd+1);
exec_line++;
} else {
close_file:
scriptstack[scriptlevel].m_file.close();
exec_line = 0;
scriptlevel--;
}
}
return 0;
}
void execscript(const char *script) {
char tmp_path[MAX_FILE_PATH+1];
char *filename = tmp_path;
fixupPath(tmp_path, script);
if(!strncmp(filename, "/sd/", 4)) {
filename += 3;
if(!sd.exists(filename)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": file '%s' not found.\r\n", script);
return;
}
} else {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": file '%s' not found.\r\n", script);
return;
}
if(scriptlevel >= MAX_SCRIPTLEVEL-1) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Script stack space exhausted.\r\n");
return;
}
scriptlevel++;
scriptstack[scriptlevel].m_file = sd.open(filename, O_RDONLY);
if(scriptstack[scriptlevel].m_file.isOpen()) {
strcpy(exec_filename, script);
exec_line = 1;
} else {
scriptlevel--;
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unable to open script '%s'.\r\n", script);
return;
}
}
void execcmd(int argc, char **argv) {
if(argc > 1) {
execscript(argv[1]);
}
}
void echocmd(int argc, char **argv) {
if(argc > 1) {
if(!strcmp("off", argv[1])) {
echo_on = 0;
} else if(!strcmp("on", argv[1])) {
echo_on = 1;
} else {
for(int i = 1; i<argc; i++) {
Serial.printf("%s ", argv[i]);
}
Serial.printf("\r\n");
}
}
}
void cmdCommandHelp(boolean singleCommand, Commands_t *table, int cmd) {
if(!table) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Command table invalid.\r\n");
return;
}
if(singleCommand && table[cmd].FullHelp) {
Serial.print(table[cmd].FullHelp);
} else if(table[cmd].ShortHelp) {
Serial.print("\r\n");
Serial.print(table[cmd].Name);
for(uint8_t i = 0; i < (16 - strlen(table[cmd].Name)); i++) {
Serial.print(" ");
}
Serial.printf("%s\r\n", table[cmd].ShortHelp);
if(table[cmd].MinParams) {
Serial.printf(" (Requires %d %s)\r\n", table[cmd].MinParams, (table[cmd].MinParams>1) ? "Parameters" : "Parameter");
}
if(singleCommand)
Serial.print("\r\n");
}
}
void cmdDispatchHelp(Commands_t *table, int argc, char **argv) {
if(!table) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": invalid command table.\r\n");
return;
}
if(argc<2) {
for(int ii=0; table[ii].Name; ii++) {
cmdCommandHelp(false, table, ii);
}
Serial.print("\r\n");
return;
}
for(int ii=0; table[ii].Name; ii++) {
if(!strcmp(argv[1], table[ii].Name)) {
if(table[ii].Dispatch) {
cmdDispatchHelp(table[ii].Dispatch, argc-1, &argv[1]);
} else {
cmdCommandHelp(true, table, ii);
}
return;
}
}
}
void clearcmd(int argc, char **argv) {
Serial.printf("\x1b[2J\x1b[H");
}
void help(int argc, char **argv) {
cmdDispatchHelp(GlobalCommands, argc, argv);
}
void cmdDispatch(Commands_t *table, int argc, char **argv) {
int ii;
if(!table) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Command table invalid.\r\n");
return;
}
argc--;
if(argc<1) {
for(ii=0; table[ii].Name; ii++) {
cmdCommandHelp(false, table, ii);
}
return;
}
for(ii=0; table[ii].Name; ii++) {
if(!strcmp(argv[1], table[ii].Name)) {
if(argc < table[ii].MinParams) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Not enough parameters given\r\n");
return;
}
table[ii].Func(argc, &argv[1]);
}
}
}
void fixupPath(char *out, const char *in) {
char *parent;
char tmp_path[MAX_FILE_PATH+1];
if(!in) return;
if(!out) return;
//unquote(tmp_path, in);
strcpy(tmp_path, in);
// Skip leading "./" if present
if(!strncmp(tmp_path, "./", 2)) {
sprintf(out, "%s/%s", cmd_prefix, tmp_path+2);
} else if(!strcmp(tmp_path, "..")) {
// Ascend if ".."
strcpy(tmp_path, cmd_prefix);
parent = strrchr(tmp_path, '/');
if(parent && (tmp_path != parent)) {
parent[0] = 0;
}
strcpy(out, tmp_path);
} else if(*tmp_path == '/') {
// Fully qualified path, replace everything
strcpy(out, tmp_path);
} else {
// Relative path, append
parent = strrchr(cmd_prefix, '/');
if(parent[1] != 0) {
sprintf(out, "%s/%s", cmd_prefix, tmp_path);
} else {
sprintf(out, "%s%s", cmd_prefix, tmp_path);
}
}
// Make sure there is not a trailing /
parent = strrchr(out, '/');
if((parent != out) && (parent[1] == 0)) {
parent[0] = 0;
}
}
// Remove any command prefix levels
void changeDirectory(int argc, char **argv) {
char new_prefix[256];
char *suffix = NULL;
if(argc>=2) {
// Parse the path string
fixupPath(new_prefix, argv[1]);
if(!strncmp("/vdevs/vdev", new_prefix, 11)) {
char *vdev_name = new_prefix + 11;
uint8_t v = strtol(vdev_name, &suffix, 0);
if((v < NUM_VDEV) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
sprintf(cmd_prefix, "/vdevs/vdev%d", v);
return;
}
goto e_invalidpath;
}
if(!strncmp("/tgts/tgt", new_prefix, 9)) {
char *tgt_name = new_prefix + 9;
char *lun_name = NULL;
uint8_t id = strtol(tgt_name, &lun_name, 0);
if(id < NUM_SCSIID) {
if(!lun_name || (lun_name[0] == 0) || !strcmp(lun_name, "/")) {
sprintf(cmd_prefix, "/tgts/tgt%d", id);
return;
} else if(!strncmp("/lun", lun_name, 4)) {
lun_name += 4;
uint8_t lun = strtol(lun_name, &suffix, 0);
if((lun < NUM_SCSILUN) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
sprintf(cmd_prefix, "/tgts/tgt%d/lun%d", id, lun);
return;
}
}
}
goto e_invalidpath;
}
if(!strcmp(new_prefix, "/") || !strcmp(new_prefix, "/tgts") || !strcmp(new_prefix, "/vdevs") || !strcmp(new_prefix, "/nv") || !strcmp(new_prefix, "/raw")
|| !strcmp(new_prefix, "/sd") || !strcmp(new_prefix, "/diag")|| !strcmp(new_prefix, "/diag/sd")
) {
strcpy(cmd_prefix, new_prefix);
return;
} else if(!strncmp(new_prefix, "/sd/", 4)) {
if((new_prefix[4] == 0) || sd.exists(new_prefix+3)) {
strcpy(cmd_prefix, new_prefix);
return;
}
}
e_invalidpath:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Path '%s' not found.\r\n", new_prefix);
} else {
Serial.printf("%s\r\n", cmd_prefix);
}
}
char tmp_left[256];
char tmp_right[256];
void printDirectory(int level, uint8_t lines, const char *l, const char *r) {
for(int pad = 0; pad < level; pad++) {
if(lines & (1 << (level - pad))) {
Serial.print("| ");
} else {
Serial.print(" ");
}
}
Serial.print("o- ");
int dots = 78 - ((level*2) + 3 + strlen(l) + strlen(r));
Serial.print(l);
while(dots--) {
Serial.print(".");
}
Serial.print(r);
Serial.print("\r\n");
}
void showVDEV(int level, uint8_t lines, uint8_t v) {
VirtualDevice_t *h = &m_vdev[v];
if(h->m_enabled) {
sprintf(tmp_left, "vdev%d/ ", v);
printDirectory(level, lines, tmp_left, " [...]");
level++;
lines<<=1;
lines|=1;
switch(h->m_type) {
case DEV_DISK:
strcpy(tmp_right, " [disk]");
break;
case DEV_OPTICAL:
strcpy(tmp_right, " [optical]");
break;
case DEV_TAPE:
strcpy(tmp_right, " [tape]");
break;
default:
strcpy(tmp_right, " [unknown]");
break;
}
printDirectory(level, lines, "$type ", tmp_right);
if((h->m_id < NUM_SCSIID) && (h->m_lun < NUM_SCSILUN)) {
sprintf(tmp_right, " [/tgts/tgt%d/lun%d]", h->m_id, h->m_lun);
printDirectory(level, lines, "$mapping", tmp_right);
}
if(h->m_file) {
sprintf(tmp_right, " [%s]", h->m_filename);
printDirectory(level, lines, "$filename ", tmp_right);
}
if((h->m_fileSize) && (h->m_blocksize)) {
sprintf(tmp_right, " [%llu]", h->m_fileSize / h->m_blocksize);
printDirectory(level, lines, "$blocks ", tmp_right);
}
if(h->m_blocksize) {
sprintf(tmp_right, " [%d]", h->m_blocksize);
printDirectory(level, lines, "$blocksize ", tmp_right);
}
}
}
void showLUN(int level, uint8_t lines, uint8_t id, uint8_t lun) {
if(!level) {
sprintf(tmp_left, "/tgts/tgt%d/lun%d/ ", id, lun);
} else {
sprintf(tmp_left, "lun%d/ ", lun);
}
uint8_t v = m_vdevmap[id][lun];
if(v < NUM_VDEV) {
printDirectory(level, lines, tmp_left, " [...]");
level++;
lines <<= 1;
lines |= 1;
sprintf(tmp_right, " [/vdevs/vdev%d]", v);
printDirectory(level, lines, "$mapping", tmp_right);
}
}
void showTarget(int level, uint8_t lines, uint8_t id) {
if(!level) {
sprintf(tmp_left, "/tgts/tgt%d/ ", id);
} else {
sprintf(tmp_left, "tgt%d/ ", id);
}
if(scsi_id_mask & (1<<id)) {
printDirectory(level, lines, tmp_left, " [...]");
level++;
lines <<= 1;
lines |= 1;
for(uint8_t lun = 0; lun < NUM_SCSILUN; lun++) {
showLUN(level, lines, id, lun);
}
} else {
printDirectory(level, lines, tmp_left, " [DISABLED]");
}
}
void showDirectory(int argc, char **argv) {
char *local_prefix = cmd_prefix;
char tmp_prefix[256];
char *suffix = NULL;
uint8_t v;
if(argc == 2) {
fixupPath(tmp_prefix, argv[1]);
local_prefix = tmp_prefix;
}
if(!strcmp(local_prefix, "/")) {
printDirectory(0, 0, "/ ", " [...]");
printDirectory(1, 0, "diag/ ", " [...]");
printDirectory(1, 0, "sd/ ", " [...]");
printDirectory(1, 0, "raw/ ", " [...]");
sprintf(tmp_right, " [%d Target%s]", NUM_SCSIID, (NUM_SCSIID != 1) ? "s" : "");
printDirectory(1, 0, "tgts/ ", tmp_right);
sprintf(tmp_right, " [%d Storage Object%s]", m_vdevcnt, (m_vdevcnt != 1) ? "s" : "");
printDirectory(1, 0, "vdevs/ ", tmp_right);
return;
}
if(!strcmp(local_prefix, "/diag")) {
printDirectory(0, 0, "/diag/ ", " [...]");
printDirectory(1, 0, "sd/ ", " [...]");
return;
}
if(!strcmp(local_prefix, "/raw")) {
mbr_t *mbr = (mbr_t *)cardMBR;
printDirectory(0, 0, "/raw/ ", " [...]");
sd.card()->readSector(0, cardMBR);
for(uint8_t partIndex = 0; partIndex < 4; partIndex++) {
sprintf(tmp_left, "part%d", partIndex);
switch(mbr->part[partIndex].type) {
case 0x04:
case 0x06:
case 0x0B:
case 0x0C:
case 0x0E:
printDirectory(1, 0, tmp_left, "[FAT Filesystem]");
break;
case 0x07:
printDirectory(1, 0, tmp_left, "[EXFAT/NTFS Filesystem]");
break;
case 0x87:
sprintf(tmp_right, "[Emulated %dMB Drive]", (int)(mbr->part[partIndex].totalSectors / 2048));
printDirectory(1, 0, tmp_left, tmp_right);
break;
}
}
return;
}
if(!strcmp(local_prefix, "/diag/sd")) {
printDirectory(0, 0, "/diag/sd/ ", " [...]");
sprintf(tmp_right, " [SdFat %s]", SD_FAT_VERSION_STR);
printDirectory(1, 0, "$version ", tmp_right);
switch(SDFAT_FILE_TYPE) {
case 0:
strcpy(tmp_right, " [FAT16]");
break;
case 1:
strcpy(tmp_right, " [FAT16/FAT32]");
break;
case 2:
strcpy(tmp_right, " [ExFAT]");
break;
case 3:
strcpy(tmp_right, " [FAT16/FAT32/ExFAT]");
break;
}
printDirectory(1, 0, "$filesystems ", tmp_right);
sprintf(tmp_right, " [%d]", MAX_FILE_PATH);
printDirectory(1, 0, "$max_filename_length ", tmp_right);
csd_t sd_csd;
if(sd.card()->readCSD(&sd_csd))
{
switch (sd.card()->type()) {
case SD_CARD_TYPE_SD1:
strcpy(tmp_right, " [SD1]");
break;
case SD_CARD_TYPE_SD2:
strcpy(tmp_right, " [SD2]");
break;
case SD_CARD_TYPE_SDHC:
if (sdCardCapacity(&sd_csd) < 70000000) {
strcpy(tmp_right, " [SDHC]");
} else {
strcpy(tmp_right, " [SDXC]");
}
break;
default:
strcpy(tmp_right, " [unknown]");
break;
}
printDirectory(1, 0, "$card_type ", tmp_right);
uint32_t cardsize = 0.000512 * sdCardCapacity(&sd_csd);
if(cardsize >= 10000) {
sprintf(tmp_right, " [%lu GB]", cardsize);
} else {
sprintf(tmp_right, " [%lu MB]", cardsize);
}
printDirectory(1, 0, "$card_size ", tmp_right);
}
cid_t sd_cid;
if(sd.card()->readCID(&sd_cid))
{
sprintf(tmp_right, " [0x%02X]", sd_cid.mid);
printDirectory(1, 0, "$card_manufacturer ", tmp_right);
sprintf(tmp_right, " [%c%c]", sd_cid.oid[0], sd_cid.oid[1]);
printDirectory(1, 0, "$card_oem ", tmp_right);
sprintf(tmp_right, " [%c%c%c%c%c]", sd_cid.pnm[0], sd_cid.pnm[1], sd_cid.pnm[2], sd_cid.pnm[3], sd_cid.pnm[4]);
printDirectory(1, 0, "$card_name ", tmp_right);
sprintf(tmp_right, " [%d.%d]", sd_cid.prv_n, sd_cid.prv_m);
printDirectory(1, 0, "$card_version ", tmp_right);
sprintf(tmp_right, " [%02d/20%02d]", sd_cid.mdt_month, (sd_cid.mdt_year_high << 4) | sd_cid.mdt_year_low);
printDirectory(1, 0, "$card_date ", tmp_right);
sprintf(tmp_right, " [%08lX]", sd_cid.psn);
printDirectory(1, 0, "$card_serial ", tmp_right);
}
return;
}
if(!strcmp(local_prefix, "/tgts")) {
sprintf(tmp_right, " [%d Target%s]", NUM_SCSIID, (NUM_SCSIID != 1) ? "s" : "");
printDirectory(0, 0, "/tgts/ ", tmp_right);
for(uint8_t id = 0; id < NUM_SCSIID; id++) {
showTarget(1, 1, id);
}
return;
}
if(!strncmp(local_prefix, "/tgts/tgt", 9)) {
char *tgt_name = local_prefix + 9;
char *lun_name = NULL;
uint8_t id = strtol(tgt_name, &lun_name, 0);
if(id < NUM_SCSIID) {
if(!lun_name || (lun_name[0] == 0) || !strcmp(lun_name, "/")) {
showTarget(0, 0, id);
return;
} else if(!strncmp("/lun", lun_name, 4)) {
lun_name += 4;
uint8_t lun = strtol(lun_name, &suffix, 0);
if((lun < NUM_SCSILUN) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
showLUN(0, 0, id, lun);
return;
}
}
}
}
if(!strcmp(local_prefix, "/vdevs")) {
sprintf(tmp_right, " [%d Storage Objects]", m_vdevcnt);
printDirectory(0, 0, "/vdevs/ ", tmp_right);
for(v = 0; v < NUM_VDEV; v++) {
showVDEV(1, 1, v);
}
return;
}
if(!strncmp(local_prefix, "/vdevs/vdev", 11)) {
char *vdev_name = local_prefix + 11;
v = strtol(vdev_name, &suffix, 0);
if((v < NUM_VDEV) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
showVDEV(0, 0, v);
return;
}
}
if(!strcmp(local_prefix, "/nv") || !strncmp(local_prefix, "/nv/", 4)) {
File root = lfs.open("/");
while(true) {
File entry = root.openNextFile();
if(! entry) {
break;
}
if(!entry.isDirectory()) {
sprintf(tmp_left, "%s ", entry.name());
if(entry.size() >= 8192) {
sprintf(tmp_right, " [%llu KB]", entry.size() >> 10);
} else {
sprintf(tmp_right, " [%llu Bytes]", entry.size());
}
printDirectory(1, 0, tmp_left, tmp_right);
}
entry.close();
}
root.close();
}
if(!strcmp(local_prefix, "/sd") || !strncmp(local_prefix, "/sd/", 4)) {
char name[MAX_FILE_PATH+1];
SdFile root;
SdFile file;
if(!strcmp(local_prefix, "/sd"))
strcat(local_prefix, "/");
root.open(local_prefix+3);
if(!root.isDir()) {
sprintf(tmp_left, "%s ", local_prefix);
if(root.fileSize() >= 2000000000) {
sprintf(tmp_right, " [%llu GB]", root.fileSize() / 1000000000);
} else if(root.fileSize() >= 2000000) {
sprintf(tmp_right, " [%llu MB]", root.fileSize() / 1000000);
} else if(root.fileSize() >= 2000) {
sprintf(tmp_right, " [%llu KB]", root.fileSize() / 1000);
} else {
sprintf(tmp_right, " [%llu Bytes]", root.fileSize());
}
printDirectory(0, 0, tmp_left, tmp_right);
root.close();
return;
}
sprintf(tmp_left, "%s ", local_prefix);
printDirectory(0, 0, tmp_left, " [...]");
while(file.openNext(&root, O_READ)) {
file.getName(name, MAX_FILE_PATH+1);
if(file.isDir()) {
sprintf(tmp_left, "%s/ ", name);
printDirectory(1, 0, tmp_left, " [...]");
} else {
sprintf(tmp_left, "%s ", name);
if(file.fileSize() >= 2000000000) {
sprintf(tmp_right, " [%llu GB]", file.fileSize() / 1000000000);
} else if(file.fileSize() >= 2000000) {
sprintf(tmp_right, " [%llu MB]", file.fileSize() / 1000000);
} else if(file.fileSize() >= 2000) {
sprintf(tmp_right, " [%llu KB]", file.fileSize() / 1000);
} else {
sprintf(tmp_right, " [%llu Bytes]", file.fileSize());
}
printDirectory(1, 0, tmp_left, tmp_right);
}
file.close();
}
root.close();
return;
}
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Path '%s' not found.\r\n", local_prefix);
}
void unlinkcmd(int argc, char **argv) {
FsFile file; /* File object */
char tmp_path[MAX_FILE_PATH+1];
char *vdev_name = NULL;
char *tgt_name = NULL;
char *lun_name = NULL;
char *suffix = NULL;
uint8_t v = 0xff;
uint8_t id = 0xff;
uint8_t lun = 0xff;
VirtualDevice_t *h = NULL;
if(argc < 2) {
return;
}
fixupPath(tmp_path, argv[1]);
if(!strncmp("/tgts/tgt", tmp_path, 9)) {
tgt_name = tmp_path + 9;
id = strtol(tgt_name, &lun_name, 0);
if(id < NUM_SCSIID) {
if((lun_name) && !strncmp("/lun", lun_name, 4)) {
// Disable a specific LUN
lun_name += 4;
lun = strtol(lun_name, &suffix, 0);
if((lun < NUM_SCSILUN) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
v = m_vdevmap[id][lun];
if(v < NUM_VDEV) {
h = &m_vdev[v];
h->m_id = 0xff;
h->m_lun = 0xff;
m_vdevmap[id][lun] = 0xff;
return;
}
}
} else if(!lun_name || (lun_name[0] == 0) || !strcmp(lun_name, "/")) {
// Disable this target and all LUNs under it
scsi_id_mask &= ~(1 << id);
for(lun = 0; lun < NUM_SCSILUN; lun++) {
v = m_vdevmap[id][lun];
if(v < NUM_VDEV) {
h = &m_vdev[v];
h->m_id = 0xff;
h->m_lun = 0xff;
m_vdevmap[id][lun] = 0xff;
}
}
return;
}
}
}
if(!strncmp("/vdevs/vdev", tmp_path, 11)) {
vdev_name = tmp_path + 11;
v = strtol(vdev_name, &suffix, 0);
if((v < NUM_VDEV) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
id = h->m_id;
lun = h->m_lun;
h = &m_vdev[v];
h->m_id = 0xff;
h->m_lun = 0xff;
if((id < NUM_SCSIID) && (lun < NUM_SCSILUN))
m_vdevmap[id][lun] = 0xff;
return;
}
}
if(!strncmp(tmp_path, "/sd/", 4)) {
sd.remove(tmp_path+3);
return;
}
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Path '%s' not found.\r\n", tmp_path);
}
void makeimagecmd(int argc, char **argv) {
FsFile file;
char tmp_path[MAX_FILE_PATH+1];
uint64_t fileSize = 0;
int i;
bool write_mbr = 0;
uint64_t cylinders = 0;
uint64_t heads = 16;
uint64_t sectors = 63;
uint64_t blocksize = 512;
tmp_path[0] = 0;
for(i = 1; i < argc; i++) {
if(argv[i][0] == '/') {
fixupPath(tmp_path, argv[i]);
if(strncmp(tmp_path, "/sd/", 4)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Can only create images on the SD Card.\r\n");
return;
}
}
if(!strcmp(argv[i], "-msdos")) {
write_mbr = 1;
} else
if(!strcmp(argv[i], "-b") && (i+1 < argc)) {
i++;
blocksize = strtoul(argv[i], NULL, 0);
} else
if(!strcmp(argv[i], "-c") && (i+1 < argc)) {
i++;
cylinders = strtoul(argv[i], NULL, 0);
} else
if(!strcmp(argv[i], "-h") && (i+1 < argc)) {
i++;
heads = strtoul(argv[i], NULL, 0);
} else
if(!strcmp(argv[i], "-s") && (i+1 < argc)) {
i++;
sectors = strtoul(argv[i], NULL, 0);
} else
{
char *suffix = NULL;
fileSize = strtoul(argv[i], &suffix, 0);
if(suffix && suffix[0] != 0) {
if(!strcmp(suffix, "KB")) fileSize *= 1000ull;
if(!strcmp(suffix, "KiB")) fileSize *= 1024ull;
if(!strcmp(suffix, "MB")) fileSize *= 1000000ull;
if(!strcmp(suffix, "MiB")) fileSize *= 1024ull * 1024ull;
if(!strcmp(suffix, "GB")) fileSize *= 1000000000ull;
if(!strcmp(suffix, "GiB")) fileSize *= 1024ull * 1024ull * 1024ull;
}
}
}
if(tmp_path[0] == 0) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": no path specified.\r\n");
return;
}
if((fileSize == 0) && (cylinders != 0)) {
fileSize = cylinders * heads * sectors * blocksize;
} else if(fileSize != 0){
// Fixup image size to our CHS formula
// Capping out at ~2GB
cylinders = fileSize / (blocksize * heads * sectors);
if(fileSize & 0xFFFFF) cylinders++;
if(cylinders > 4095) cylinders = 4095;
fileSize = cylinders * (blocksize * heads * sectors);
} else {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": no image size specified.\r\n");
return;
}
if(sd.exists(tmp_path+3)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": file '%s' already exists.\r\n", tmp_path);
return;
}
if(fileSize < (5ull * 1024ull * 1024ull)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Image would be less than 5 MiB.\r\n");
return;
}
if(fileSize > (2048ull * 1024ull * 1024ull)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Image would be larger than 2 GiB.\r\n");
return;
}
if(!file.open(tmp_path+3, O_WRONLY | O_CREAT | O_TRUNC)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unable to open '%s'.\r\n", tmp_path);
} else {
if(write_mbr) {
file.write(mbr_bin, 512);
fileSize -= 512;
}
memset(zero, 0, 512);
while(fileSize) {
if((fileSize & 0x1FFF) == 0)
Serial.printf(".");
if((fileSize & 0x7FFFF) == 0)
Serial.printf("\r\n");
file.write(zero, (fileSize < 512) ? fileSize : 512);
fileSize -= (fileSize < 512) ? fileSize : 512;
}
Serial.printf("\r\n");
file.close();
return;
}
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unable to process command as requested.\r\n");
return;
}
void catcmd(int argc, char **argv) {
FsFile file; /* File object */
char tmp_path[MAX_FILE_PATH+1];
char tmp_str[256];
char *filename = tmp_path;
if(argc < 2) {
return;
}
fixupPath(tmp_path, argv[1]);
if(!strncmp(filename, "/nv/", 4)) {
filename += 3;
} else if(!strncmp(filename, "/sd/", 4)) {
filename += 3;
} else {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": file '%s' not found.\r\n", tmp_path);
return;
}
if(!sd.exists(filename)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": file '%s' not found.\r\n", tmp_path);
return;
}
if(!file.open(filename, O_RDONLY)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unable to open '%s'.\r\n", tmp_path);
} else {
while(file.available()) {
int n = file.fgets(tmp_str, sizeof(tmp_str)-1, NULL);
if (n <= 0) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Reading '%s' failed.\r\n", tmp_path);
break;
}
Serial.print(tmp_str);
}
file.close();
}
}
void enablecmd(int argc, char **argv) {
uint8_t id = 0xff;
char *tgt_name = NULL;
char *suffix;
char tmp_path[MAX_FILE_PATH+1];
// The logic here is slightly complicated to allow specifying enable /tgts/tgtN from anywhere, but otherwise require we are in /tgts
if((argc<2) && strncmp("/tgts", cmd_prefix, 5)) {
goto e_invalidpath;
}
if((argc<2) && !strcmp("/tgts", cmd_prefix)) {
goto e_notarget;
}
if(argc<2) {
strcpy(tmp_path, cmd_prefix);
} else {
fixupPath(tmp_path, argv[1]);
}
if(strncmp("/tgts/", tmp_path, 6)) {
goto e_invalidpath;
}
if(!strncmp("/tgts/tgt", tmp_path, 9)) {
tgt_name = tmp_path + 9;
id = strtol(tgt_name, &suffix, 0);
}
if(id < NUM_SCSIID) {
scsi_id_mask |= (1<<id);
return;
}
//e_invalidtarget:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": Invalid target specified.\r\n");
return;
e_invalidpath:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": The enable command is not valid in this context.\r\n");
return;
e_notarget:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": No target specified.\r\n");
return;
}
void setcmd(int argc, char **argv) {
uint8_t v = 0xff;
char *vdev_name = NULL;
char *param_name = NULL;
char tmp_path[MAX_FILE_PATH+1];
if(argc<2) {
goto e_syntax;
}
fixupPath(tmp_path, argv[1]);
// Allow specifying set /vdevs/vdevN/parameter from anywhere, but otherwise require we are in /vdevs/vdevN
if(!strncmp("/global", tmp_path, 7)) {
param_name = tmp_path + 7;
if(!strcasecmp(param_name, "/quirks")) {
if(argc<3) {
Serial.printf("0x%02x\r\n", default_quirks);
} else {
default_quirks = strtol(argv[2], NULL, 0);
}
return;
}
}
// Allow specifying set /vdevs/vdevN/parameter from anywhere, but otherwise require we are in /vdevs/vdevN
if(!strncmp("/vdevs/vdev", tmp_path, 11)) {
vdev_name = tmp_path + 11;
v = strtol(vdev_name, &param_name, 0);
if(v < NUM_VDEV) {
VirtualDevice_t *h = &m_vdev[v];
if((param_name) && !strcasecmp(param_name, "/blocksize")) {
if(argc<3) {
Serial.printf("%d\r\n", h->m_blocksize);
} else {
h->m_blocksize = strtol(argv[2], NULL, 0);
}
return;
}
if((param_name) && !strcasecmp(param_name, "/sectors")) {
if(argc<3) {
Serial.printf("%d\r\n", h->m_sectors);
} else {
h->m_blocksize = strtol(argv[2], NULL, 0);
}
return;
}
if((param_name) && !strcasecmp(param_name, "/heads")) {
if(argc<3) {
Serial.printf("%d\r\n", h->m_heads);
} else {
h->m_blocksize = strtol(argv[2], NULL, 0);
}
return;
}
if((param_name) && !strcasecmp(param_name, "/cylinders")) {
if(argc<3) {
Serial.printf("%d\r\n", h->m_cylinders);
} else {
h->m_blocksize = strtol(argv[2], NULL, 0);
}
return;
}
if((param_name) && !strcasecmp(param_name, "/quirks")) {
if(argc<3) {
Serial.printf("0x%02x\r\n", h->m_quirks);
} if(!strcasecmp(argv[2], "SASI") || !strcasecmp(argv[2], "+SASI")) {
h->m_quirks |= QUIRKS_SASI;
} if(!strcasecmp(argv[2], "-SASI")) {
h->m_quirks &= ~QUIRKS_SASI;
} if(!strcasecmp(argv[2], "APPLE") || !strcasecmp(argv[2], "+APPLE")) {
h->m_quirks |= QUIRKS_APPLE;
} if(!strcasecmp(argv[2], "-APPLE")) {
h->m_quirks &= ~QUIRKS_APPLE;
} else {
h->m_quirks = strtol(argv[2], NULL, 0);
}
return;
}
if((param_name) && !strcasecmp(param_name, "/type")) {
if(argc<3) {
switch(h->m_type) {
case DEV_DISK:
strcpy(tmp_right, " [disk]");
break;
case DEV_OPTICAL:
strcpy(tmp_right, " [optical]");
break;
case DEV_TAPE:
strcpy(tmp_right, " [tape]");
break;
default:
strcpy(tmp_right, " [unknown]");
break;
}
printDirectory(0, 0, "$type ", tmp_right);
} else {
#if SUPPORT_DISK
if(!strcasecmp(argv[2], "disk")) {
ConfigureDisk(h, NULL);
return;
}
#endif /* SUPPORT_DISK */
#if SUPPORT_OPTICAL
if(!strcasecmp(argv[2], "optical") || !strcasecmp(argv[2], "cdrom")) {
ConfigureOptical(h, NULL);
return;
}
#endif /* SUPPORT_OPTICAL */
#if SUPPORT_TAPE
if(!strcasecmp(argv[2], "tape")) {
ConfigureTape(h, NULL);
return;
}
#endif /* SUPPORT_TAPE */
}
goto e_invalidvalue;
}
goto e_invalidparam;
}
goto e_invalidpath;
}
e_invalidpath:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": The set command is not valid in this context.\r\n");
return;
e_invalidparam:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Invalid parameter '%s' specified.\r\n", param_name);
return;
e_invalidvalue:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Invalid value '%s' for parameter '%s' specified.\r\n", argv[2], param_name);
return;
e_syntax:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": Command syntax violation.\r\n");
return;
}
void createcmd(int argc, char **argv) {
if(strncmp("/vdev", cmd_prefix, 5)) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": The create command is not valid in this context.\r\n");
return;
}
if(m_vdevcnt >= NUM_VDEV) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": Virtual Device pool exhausted.\r\n");
return;
}
if(argc<2) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": No device type specified.\r\n");
return;
}
uint8_t v = m_vdevcnt;
VirtualDevice_t *h = &m_vdev[v];
h->m_quirks = default_quirks;
#if SUPPORT_DISK
if(!strcasecmp(argv[1], "disk")) {
ConfigureDisk(h, NULL);
goto success;
}
#endif /* SUPPORT_DISK */
#if SUPPORT_OPTICAL
if(!strcasecmp(argv[1], "optical") || !strcasecmp(argv[1], "cdrom")) {
ConfigureOptical(h, NULL);
goto success;
}
#endif /* SUPPORT_OPTICAL */
#if SUPPORT_TAPE
if(!strcasecmp(argv[1], "tape")) {
ConfigureTape(h, NULL);
goto success;
}
#endif /* SUPPORT_TAPE */
#if SUPPORT_CONTROL
if(!strcasecmp(argv[1], "control")) {
ConfigureControl(h, NULL);
goto success;
}
#endif /* SUPPORT_CONTROL */
//failure:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": Unknown device type specified.\r\n");
return;
success:
sprintf(cmd_prefix, "/vdevs/vdev%d", v);
h->m_enabled = true;
h->m_id = 0xff;
h->m_lun = 0xff;
m_vdevcnt++;
}
void mountcmd(int argc, char **argv) {
char tmp_path[MAX_FILE_PATH+1];
char *vdev_name = NULL;
VirtualDevice_t *h;
uint8_t v = 0xff;
if(!strncmp("/vdevs/vdev", cmd_prefix, 11)) {
vdev_name = cmd_prefix + 11;
v = strtol(vdev_name, NULL, 0);
}
if(v < NUM_VDEV) {
h = &m_vdev[v];
if(h->m_blocksize == 0) {
if(h->m_type == DEV_TAPE) {
h->m_blocksize = 1024;
} else {
h->m_blocksize = 512;
}
}
} else if(argc>=2) {
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": The mount command is not valid in this context.\r\n");
return;
} else {
// TODO: Print out all mounted images
return;
}
if(argc<2) {
// TODO: Print out the mounted image
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": No backingstore specified.\r\n");
return;
}
fixupPath(tmp_path, argv[1]);
if(!strncmp(tmp_path, "/sd/", 4)) {
if(OpenDiskImage(h, tmp_path, 512)) {
strcpy(h->m_filename, tmp_path);
h->m_enabled = true;
return;
}
}
if(!strncmp(tmp_path, "/raw/", 5)) {
if(OpenDiskImage(h, tmp_path, 512)) {
strcpy(h->m_filename, tmp_path);
h->m_enabled = true;
return;
}
}
h->m_fileSize = 0;
h->m_blocksize = 0;
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": Unsupported backingstore path.\r\n");
return;
}
void mapcmd(int argc, char **argv) {
char tmp_path[MAX_FILE_PATH+1];
char *vdev_name = NULL;
char *tgt_name = NULL;
char *lun_name = NULL;
char *suffix = NULL;
VirtualDevice_t *h;
uint8_t v = 0xff;
uint8_t id = 0xff;
uint8_t lun = 0xff;
if(!strncmp("/vdevs/vdev", cmd_prefix, 11)) {
vdev_name = cmd_prefix + 11;
v = strtol(vdev_name, &suffix, 0);
if((v < NUM_VDEV) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
h = &m_vdev[v];
if(argc>=2) {
fixupPath(tmp_path, argv[1]);
if(!strncmp("/tgts/tgt", tmp_path, 9)) {
tgt_name = tmp_path + 9;
id = strtol(tgt_name, &lun_name, 0);
if((id < NUM_SCSIID) && (lun_name)) {
if(!strncmp("/lun", lun_name, 4)) {
lun_name += 4;
lun = strtol(lun_name, &suffix, 0);
if((lun < NUM_SCSILUN) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
h->m_id = id;
h->m_lun = lun;
m_vdevmap[id][lun] = v;
return;
}
}
}
}
goto e_invalidtarget;
} else {
sprintf(tmp_left, "/vdevs/vdev%d/$mapping ", v);
sprintf(tmp_right, " [/tgts/tgt%d/lun%d]\r\n", h->m_id, h->m_lun);
printDirectory(0, 0, tmp_left, tmp_right);
return;
}
}
}
if(!strncmp("/tgts/tgt", cmd_prefix, 9)) {
tgt_name = cmd_prefix + 9;
id = strtol(tgt_name, &lun_name, 0);
if((id < NUM_SCSIID) && (lun_name)) {
if(!strncmp("/lun", lun_name, 4)) {
lun_name += 4;
lun = strtol(lun_name, &suffix, 0);
if((lun < NUM_SCSILUN) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
if(argc>=2) {
fixupPath(tmp_path, argv[1]);
if(!strncmp("/vdevs/vdev", tmp_path, 11)) {
vdev_name = cmd_prefix + 11;
v = strtol(vdev_name, &suffix, 0);
if((v < NUM_VDEV) && (!suffix || (suffix[0] == 0) || !strcmp(suffix, "/"))) {
h = &m_vdev[v];
h->m_id = id;
h->m_lun = lun;
m_vdevmap[id][lun] = v;
return;
}
}
goto e_invalidtarget;
} else {
v = m_vdevmap[id][lun];
if(v < NUM_VDEV) {
sprintf(tmp_left, "/tgts/tgt%d/lun%d/$mapping ", id, lun);
sprintf(tmp_right, " [/vdevs/vdev%d]\r\n", v);
printDirectory(0, 0, tmp_left, tmp_right);
}
return;
}
}
}
}
}
//e_invalidcontext:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.print(": The map command is not valid in this context.\r\n");
return;
e_invalidtarget:
errorlevel = -1;
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Invalid target '%s' specified.\r\n", tmp_path);
return;
}
void saveconfig(int argc, char **argv) {
FsFile config_file;
config_file = sd.open("/greenscsi.cfg", O_WRONLY | O_CREAT | O_TRUNC);
if(!config_file.isOpen()) {
Serial.print("ERROR");
if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename);
Serial.printf(": Unable to open 'greenscsi.cfg' for writing.\r\n");
return;
}
config_file.print( "@echo off\r\n\r\n");
if(scsi_id_mask != 0) {
config_file.print( "cd /tgts\r\n");
for(uint8_t id = 0; id < NUM_SCSIID; id++) {
if(scsi_id_mask & (1<<id)) {
config_file.printf("enable tgt%d\r\n", id);
}
}
}
config_file.print( "\r\n");
config_file.print( "cd /vdevs\r\n");
for(uint8_t id = 0; id < NUM_SCSIID; id++) {
for(uint8_t lun = 0; lun < NUM_SCSILUN; lun++) {
uint8_t v = m_vdevmap[id][lun];
if(v < NUM_VDEV) {
VirtualDevice_t *h = &m_vdev[v];
if(h->m_enabled) {
// The create command automatically changes directory to the created object
switch(h->m_type) {
case DEV_PROCESSOR:
config_file.print("create control\r\n");
break;
case DEV_DISK:
config_file.print("create disk\r\n");
break;
case DEV_OPTICAL:
config_file.print("create optical\r\n");
break;
case DEV_TAPE:
config_file.print("create tape\r\n");
break;
default:
config_file.printf("create 0x%02x\r\n", h->m_type);
break;
}
if(h->m_file) {
config_file.printf("mount %s\r\n", h->m_filename);
}
if(h->m_blocksize) {
config_file.printf("set blocksize %d\r\n", h->m_blocksize);
}
// The map command should be last, as it will escape back to /vdevs
if((h->m_id < NUM_SCSIID) && (h->m_lun < NUM_SCSILUN)) {
config_file.printf("map /tgts/tgt%d/lun%d\r\n", h->m_id, h->m_lun);
} else {
config_file.print("cd ..\r\n");
}
config_file.print( "\r\n");
}
}
}
}
config_file.close();
}
char helponSelfTest[] =
"\r\n"
"selftest\r\n"
"\r\n"
" Use the selftest command to check for miswired boards and shorts.\r\n"
"\r\n"
;
char helponSave[] =
"\r\n"
"saveconfig\r\n"
"\r\n"
" Generate a new greenscsi.cfg file containing currently configured parameters.\r\n"
"Any existing greenscsi.cfg file will be overwritten and its previous contents lost.\r\n"
"\r\n"
;
char helponSet[] =
"\r\n"
"set <key> <value>\r\n"
"\r\n"
" Use the set command to configure vdev parameters.\r\n"
"\r\n"
;
char helponCreate[] =
"\r\n"
"create <type>\r\n\r\n"
" The create command allocates and changes the path to a newly created vdev.\r\n"
"Recognized values for type:"
#if SUPPORT_DISK
" disk"
#endif
#if SUPPORT_OPTICAL
" optical"
#endif
#if SUPPORT_TAPE
" tape"
#endif
".\r\n"
"You can also specify the SCSI device code value directly.\r\n"
"\r\n"
;
char helponEcho[] =
"\r\n"
"echo [on|off|<message>]\r\n"
"\r\n"
" The echo command allows you to turn on or off the echoing of script commands to the console.\r\n"
"Additionally, you can use the echo command to relay a message to the console.\r\n"
"\r\n"
;
char helponCat[] =
"\r\n"
"cat <file>\r\n"
"\r\n"
" The cat command dumps files to the console for reading.\r\n"
"\r\n"
;
char helponUnlink[] =
"\r\n"
"unlink <path>\r\n"
"\r\n"
" The unlink command removes objects such as files or vdevs.\r\n"
"\r\n"
;
char helponExec[] =
"\r\n"
"exec <script>\r\n"
"\r\n"
" The exec command executes command script files.\r\n"
"At startup, greenscsi.cfg will be executed from the sd card if found.\r\n"
"\r\n"
;
char helponEnable[] =
"\r\n"
"enable tgt<n>\r\n"
"\r\n"
" The enable command sets a SCSI device ID to be handled by GreenSCSI.\r\n"
"\r\n"
;
char helponMapT[] =
"\r\n"
"map <vdev>\r\n"
"\r\n"
" The map command associates a given vdev with this lun.\r\n"
"\r\n"
;
char helponMapV[] =
"\r\n"
"map <lun>\r\n"
"\r\n"
" The map command associates a given lun with this vdev.\r\n"
"\r\n"
;
char helponMount[] =
"\r\n"
"mount <path>\r\n"
"\r\n"
" The mount command associates a given image or block device with this vdev.\r\n"
"\r\n"
;
char helponVar[] =
"\r\n"
"setvar <key> <value>\r\n"
"unsetvar <key>\r\n"
"getvar <key>\r\n"
"\r\n"
" The var commands allow you to perform rudementary environment variable manipulation.\r\n"
"\r\n"
;
char helponMkImg[] =
"\r\n"
"mkimg <file> [-msdos] [-b blocksize] [-c cylinders] [-h heads] [-s sectors] [size]\r\n"
"\r\n"
" The mkimg command creates image files on FAT or ExFat volumes.\r\n"
"This command is limited to 4095 cylinders or approximately 2GB.\r\n"
"To create larger images, use a USB card reader.\r\n"
"\r\n"
" -msdos preloads an msdos compatible MBR.\r\n"
" -b sets the sector size (default=512).\r\n"
" -c sets the number of cylinders.\r\n"
" -h sets the heads (default=16).\r\n"
" -s sets the sectors per track (default=63).\r\n"
"\r\n"
"You can specify the size either via the combination of -c -h -s parameters, or\r\n"
"specify the file size with optional KB,MB,GB (1000) or KiB,MiB,GiB (1024) suffix\r\n"
"and a cylinder count close to your desired size will be chosen for you.\r\n"
"\r\n"
;
char helponHelp[] =
"\r\n"
"help\r\n"
"\r\n"
" This help facility will show you the commands available within the current path.\r\n"
"Some commands may have longer help articles such as this one to describe their parameters fully.\r\n"
"\r\n"
;
Commands_t GlobalCommands[] = {
// Command Valid From Path Req. Params Short Help Long Help Handler Dispatch
{ "selftest", "/", 0, "Execute Self Test.", helponSelfTest, SelfTest, NULL },
{ "cd", "/", 1, "change current directory", NULL, changeDirectory, NULL },
{ "sl", "/", 0, NULL, NULL, slcmd, NULL },
{ "dir", "/", 0, NULL, NULL, punishDirectory, NULL },
{ "ls", "/", 0, "display directory contents", NULL, showDirectory, NULL },
{ "saveconfig", "/", 0, "write greenscsi.cfg file", helponSave, saveconfig, NULL },
{ "enable", "/tgts", 1, "<target>", helponEnable, enablecmd, NULL },
{ "map", "/tgts/tgt", 1, "<vdev>", helponMapT, mapcmd, NULL },
{ "create", "/vdevs", 1, "<type>", helponCreate, createcmd, NULL },
{ "set", "/vdevs/vdev", 2, "<key> <value>", helponSet, setcmd, NULL },
{ "mount", "/vdevs/vdev", 1, "<path>", helponMount, mountcmd, NULL },
{ "map", "/vdevs/vdev", 1, "<lun>", helponMapV, mapcmd, NULL },
{ "cat", "/sd", 1, "<file>", helponCat, catcmd, NULL },
{ "mkimg", "/sd", 1, "<file> <size>", helponMkImg, makeimagecmd, NULL },
{ "unlink", "/", 1, "<path>", helponUnlink, unlinkcmd, NULL },
{ "exec", "/", 1, "<script>", helponExec, execcmd, NULL },
{ "goto", "/", 1, NULL, NULL, gotocmd, NULL },
{ "exit", "/", 0, NULL, NULL, exitcmd, NULL },
{ "setvar", "/", 2, "<key> <value>", helponVar, setvarcmd, NULL },
{ "getvar", "/", 0, "[key]", helponVar, getvarcmd, NULL },
{ "unsetvar", "/", 1, "<key>", helponVar, unsetvarcmd, NULL },
{ "echo", "/", 0, "[on|off|<message>]", helponEcho, echocmd, NULL },
{ "clear", "/", 0, "Erase the screen", NULL, clearcmd, NULL },
{ "help", "/", 0, "This help facility.", helponHelp, help, NULL },
{ NULL, NULL, 0, NULL, NULL, NULL, NULL }
};