diff --git a/src/cmd.ino b/src/cmd.ino index e9043ae..b2c5333 100755 --- a/src/cmd.ino +++ b/src/cmd.ino @@ -4,13 +4,27 @@ static uint8_t msg[MAX_MSG_SIZE] = ""; static uint8_t *msg_ptr = msg; -char exec_string[MAX_FILE_PATH+1] = ""; -char exec_file[MAX_FILE_PATH+1] = ""; -int exec_line = 0; 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[]; + void cmdDisplay() { if(cmd_prefix[0] != 0) { Serial.printf("[%s] $ ", cmd_prefix); @@ -98,6 +112,137 @@ void unquote(char *out, char *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; + + 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; + + 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; + + // 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; + + // 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; + } + + 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]; @@ -106,6 +251,9 @@ void cmdParse(char *cmd) { 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)) { @@ -117,17 +265,65 @@ void cmdParse(char *cmd) { // 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Unknown command '%s'.\r\n", argv[0]); } @@ -136,6 +332,7 @@ void cmd_handler() { switch (c) { case '\n': + break; case '\r': // terminate the msg and reset the msg ptr. then send // it to the handler for processing. @@ -145,23 +342,36 @@ void cmd_handler() { cmdParse((char *)msg); execLoop(); cmdDisplay(); + } else { + Serial.print("\r\n"); + cmdDisplay(); } msg_ptr = msg; break; case '\b': // backspace - Serial.print(c); 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 - Serial.print(c); - *msg_ptr++ = c; + if(msg_ptr < (msg + sizeof(msg) - 1)) { + Serial.print(c); + *msg_ptr++ = c; + *msg_ptr = '\0'; + } else { + Serial.print('\x07'); + } break; } } @@ -172,28 +382,147 @@ void cmdPoll() { } } -int execLoop() { - if(!exec_string[0]) return -1; - - char exec_command[sizeof(exec_string)]; - memset(exec_command, 0, sizeof(exec_string)); +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; + } - strncpy(exec_command, exec_string, sizeof(exec_string)-1); - memset(exec_string, 0, sizeof(exec_string)); - execHandler(exec_command); + // 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 execcmd(int argc, char **argv) { - if(exec_file[0]) { - Serial.printf("ERROR on line %d of '%s': exec called from inside a script.\r\n", exec_line, exec_file); +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) { - memset(exec_string, 0, sizeof(exec_string)); - strncpy(exec_string, argv[1], sizeof(exec_string)-1); + execscript(argv[1]); } } @@ -212,65 +541,11 @@ void echocmd(int argc, char **argv) { } } -int execHandler(char *filepath) { - FsFile file; /* File object */ - char exec_cmd[256]; - char tmp_path[MAX_FILE_PATH+1]; - char *filename = tmp_path; - - strcpy(exec_file, filepath); - - fixupPath(tmp_path, filepath); - if(!strncmp(filename, "/sd/", 4)) { - filename += 3; - } else { - Serial.printf("ERROR: file '%s' not found.\r\n", filepath); - return -1; - } - - if(!sd.exists(filename)) { - Serial.printf("ERROR: file '%s' not found.\r\n", filepath); - return -1; - } - - if(!file.open(filename, O_RDONLY)) { - Serial.printf("ERROR: Unable to open script '%s'.\r\n", filepath); - } else { - exec_line = 1; - while(file.available()) { - int n = file.fgets(exec_cmd, sizeof(exec_cmd)-1, NULL); - if (n <= 0) { - Serial.printf("Reading script '%s' failed at line %d.\r\n", filepath, exec_line); - - break; - } - 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]!='@') - Serial.printf("%s\r\n", exec_cmd); - if(exec_cmd[0] != '#') - cmdParse((exec_cmd[0]!='@') ? exec_cmd : exec_cmd+1); - - exec_line++; - } - file.close(); - exec_line = 0; - exec_file[0] = 0; - } - - strcpy(cmd_prefix, "/"); - - return 0; -} - void cmdCommandHelp(boolean singleCommand, Commands_t *table, int cmd) { if(!table) { + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Command table invalid.\r\n"); return; } @@ -293,8 +568,9 @@ void cmdCommandHelp(boolean singleCommand, Commands_t *table, int cmd) { void cmdDispatchHelp(Commands_t *table, int argc, char **argv) { if(!table) { + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": invalid command table.\r\n"); return; } @@ -317,6 +593,10 @@ void cmdDispatchHelp(Commands_t *table, int argc, char **argv) { } } +void clearcmd(int argc, char **argv) { + Serial.printf("\x1b[2J\x1b[H"); +} + void help(int argc, char **argv) { cmdDispatchHelp(GlobalCommands, argc, argv); } @@ -325,8 +605,9 @@ void cmdDispatch(Commands_t *table, int argc, char **argv) { int ii; if(!table) { + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Command table invalid.\r\n"); return; } @@ -341,8 +622,9 @@ void cmdDispatch(Commands_t *table, int argc, char **argv) { 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Not enough parameters given\r\n"); return; } @@ -351,7 +633,7 @@ void cmdDispatch(Commands_t *table, int argc, char **argv) { } } -void fixupPath(char *out, char *in) { +void fixupPath(char *out, const char *in) { char *parent; char tmp_path[MAX_FILE_PATH+1]; @@ -445,8 +727,9 @@ void changeDirectory(int argc, char **argv) { } e_invalidpath: + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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); @@ -713,10 +996,12 @@ void showDirectory(int argc, char **argv) { root.open(local_prefix+3); if(!root.isDir()) { sprintf(tmp_left, "%s ", local_prefix); - if(root.fileSize() >= 1000000) { - sprintf(tmp_right, " [%llu GB]", root.fileSize() / 1000000); - } else if(file.fileSize() >= 10000) { - sprintf(tmp_right, " [%llu MB]", root.fileSize() / 1000); + 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()); } @@ -734,10 +1019,12 @@ void showDirectory(int argc, char **argv) { printDirectory(1, 0, tmp_left, " [...]"); } else { sprintf(tmp_left, "%s ", name); - if(file.fileSize() >= 1000000) { - sprintf(tmp_right, " [%llu GB]", file.fileSize() / 1000000); - } else if(file.fileSize() >= 10000) { - sprintf(tmp_right, " [%llu MB]", file.fileSize() / 1000); + 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()); } @@ -748,8 +1035,10 @@ void showDirectory(int argc, char **argv) { root.close(); return; } + + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Path '%s' not found.\r\n", local_prefix); } @@ -805,6 +1094,23 @@ void unlinkcmd(int argc, char **argv) { } } } + 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); @@ -812,10 +1118,102 @@ void unlinkcmd(int argc, char **argv) { } Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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; + + if(argc < 4) { + return; + } + + fixupPath(tmp_path, argv[1]); + 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[2], "msdos") || !strcmp(argv[2], "generic")) { + char *suffix = NULL; + fileSize = strtoul(argv[3], &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(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; + } + + // Fixup image size to our 64 Head 32 Sector X Cylinders formula + uint64_t cyl = fileSize / (512ull * 64ull * 32ull); + if(fileSize & 0xFFFFF) cyl++; + if(cyl > 2048) cyl = 2048; + fileSize = cyl * (512ull * 64ull * 32ull); + + 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 { + // Take advantage of our cylinders being 1MB + if(!file.preAllocate(cyl)) { + file.close(); + sd.remove(tmp_path+3); + + errorlevel = -1; + Serial.print("ERROR"); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); + Serial.printf(": Pre-allocate failed, SD Card must be formatted as ExFat.\r\n"); + return; + } + + if(!strcmp(argv[2], "msdos")) { + file.write(mbr_bin, 512); + } + 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]; @@ -830,29 +1228,33 @@ void catcmd(int argc, char **argv) { if(!strncmp(filename, "/sd/", 4)) { filename += 3; } else { + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.printf(": Reading '%s' failed.\r\n", tmp_path); break; @@ -898,21 +1300,24 @@ void enablecmd(int argc, char **argv) { return; } -e_invalidtarget: +//e_invalidtarget: + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.print(": No target specified.\r\n"); return; } @@ -930,6 +1335,20 @@ void setcmd(int argc, char **argv) { 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; @@ -947,48 +1366,110 @@ void setcmd(int argc, char **argv) { return; } + if((param_name) && !strcasecmp(param_name, "/quirks")) { + if(argc<3) { + Serial.printf("0x%02x\r\n", h->m_quirks); + } 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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_syntax: +e_invalidvalue: + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.print(": No device type specified.\r\n"); return; } @@ -996,6 +1477,8 @@ void createcmd(int argc, char **argv) { 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); @@ -1015,9 +1498,10 @@ void createcmd(int argc, char **argv) { } #endif /* SUPPORT_TAPE */ -failure: +//failure: + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.print(": Unknown device type specified.\r\n"); return; @@ -1050,8 +1534,9 @@ void mountcmd(int argc, char **argv) { } } } else if(argc>=2) { + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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 { @@ -1062,7 +1547,7 @@ void mountcmd(int argc, char **argv) { if(argc<2) { // TODO: Print out the mounted image Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.print(": No backingstore specified.\r\n"); return; } @@ -1080,8 +1565,9 @@ void mountcmd(int argc, char **argv) { h->m_fileSize = 0; h->m_blocksize = 0; + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + if(scriptlevel >= 0) Serial.printf(" on line %d of '%s'", exec_line, exec_filename); Serial.print(": Unsupported backingstore path.\r\n"); return; } @@ -1170,15 +1656,17 @@ void mapcmd(int argc, char **argv) { } } -e_invalidcontext: +//e_invalidcontext: + errorlevel = -1; Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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; } @@ -1189,7 +1677,7 @@ void saveconfig(int argc, char **argv) { config_file = sd.open("/greenscsi.cfg", O_WRONLY | O_CREAT | O_TRUNC); if(!config_file.isOpen()) { Serial.print("ERROR"); - if(exec_file[0]) Serial.printf(" on line %d of '%s'", exec_line, exec_file); + 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; } @@ -1350,6 +1838,30 @@ char helponMount[] = "\r\n" ; +char helponVar[] = + "\r\n" + "setvar \r\n" + "unsetvar \r\n" + "getvar \r\n" + "\r\n" + " The var commands allow you to perform rudementary environment variable manipulation.\r\n" + "\r\n" +; + +char helponMkImg[] = + "\r\n" + "mkimg \r\n" + "\r\n" + " The mkimg command creates image files on ExFat volumes.\r\n" + "\r\n" + " specifies the partitioning scheme to preload the image with.\r\n" + " generic creates a blank disk ready for partitioning.\r\n" + " msdos preloads an MBR boot menu, and in the future may pre-partition and format.\r\n" + "\r\n" + " specifies file size and supports a KB,MB,GB (1000) or KiB,MiB,GiB (1024) suffix.\r\n" + "\r\n" +; + char helponHelp[] = "\r\n" "help\r\n" @@ -1362,6 +1874,8 @@ char helponHelp[] = Commands_t GlobalCommands[] = { // Command Valid From Path Req. Params Short Help Long Help Handler Dispatch { "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, "", helponEnable, enablecmd, NULL }, @@ -1371,9 +1885,16 @@ Commands_t GlobalCommands[] = { { "mount", "/vdevs/vdev", 1, "", helponMount, mountcmd, NULL }, { "map", "/vdevs/vdev", 1, "", helponMapV, mapcmd, NULL }, { "cat", "/sd", 1, "", helponCat, catcmd, NULL }, + { "mkimg", "/sd", 1, "", helponMkImg, makeimagecmd, NULL }, { "unlink", "/", 1, "", helponUnlink, unlinkcmd, NULL }, { "exec", "/", 1, "