shell.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. // Max length of a single command
  2. #define SHELL_CMD_MAX_LENGTH 128
  3. #define SHELL_BIN_PATH "/bin/"
  4. word shell_cmd[SHELL_CMD_MAX_LENGTH];
  5. word shell_cmd_idx = 0;
  6. word* shell_tokens[SHELL_CMD_MAX_LENGTH >> 1]; // Array of tokens in the command
  7. word shell_num_tokens = 0;
  8. word shell_path[MAX_PATH_LENGTH];
  9. /**
  10. * Print the shell prompt
  11. */
  12. void shell_print_prompt()
  13. {
  14. GFX_PrintConsole(shell_path);
  15. GFX_PrintConsole("> ");
  16. }
  17. /**
  18. * Clear the current command buffer
  19. */
  20. void shell_clear_command()
  21. {
  22. shell_cmd[0] = 0;
  23. shell_cmd_idx = 0;
  24. shell_tokens[0] = 0;
  25. shell_num_tokens = 0;
  26. }
  27. /**
  28. * Append character to command buffer
  29. */
  30. void shell_append_command(char c)
  31. {
  32. if (shell_cmd_idx < SHELL_CMD_MAX_LENGTH)
  33. {
  34. shell_cmd[shell_cmd_idx] = c;
  35. shell_cmd_idx++;
  36. shell_cmd[shell_cmd_idx] = 0; // Terminate
  37. }
  38. }
  39. /**
  40. * Parse the command buffer into tokens
  41. */
  42. void shell_parse_command()
  43. {
  44. // Strip trailing spaces
  45. word i = shell_cmd_idx - 1;
  46. while (i >= 0 && shell_cmd[i] == ' ')
  47. {
  48. shell_cmd[i] = 0;
  49. i--;
  50. }
  51. shell_num_tokens = 0;
  52. shell_tokens[shell_num_tokens] = strtok(shell_cmd, " ");
  53. while((word)shell_tokens[shell_num_tokens] != -1)
  54. {
  55. shell_num_tokens++;
  56. shell_tokens[shell_num_tokens] = strtok(-1, " ");
  57. }
  58. }
  59. /**
  60. * Print help
  61. */
  62. void shell_print_help()
  63. {
  64. GFX_PrintConsole(
  65. "************\n"
  66. "*BDOS Shell*\n"
  67. "************\n"
  68. "Available commands:\n"
  69. "- cd <path>\n"
  70. "- clear\n"
  71. "- format <blk size> <blk cnt>\n"
  72. "- sync\n"
  73. "- help\n"
  74. "\n"
  75. "Programs are run from:\n"
  76. "- /bin\n"
  77. "- <current dir>\n"
  78. );
  79. }
  80. /**
  81. * Attempt to run program from FS
  82. * If run_from_path is 1, the program is run from the SHELL_BIN_PATH
  83. * Returns 1 if program was found, 0 otherwise
  84. */
  85. word shell_run_program(word run_from_path)
  86. {
  87. // Create absolute path to program
  88. char absolute_path[MAX_PATH_LENGTH];
  89. // Check if program is relative or absolute
  90. if (shell_tokens[0][0] == '/')
  91. {
  92. strcpy(absolute_path, shell_tokens[0]);
  93. }
  94. else
  95. {
  96. if (run_from_path)
  97. {
  98. strcpy(absolute_path, SHELL_BIN_PATH);
  99. strcat(absolute_path, shell_tokens[0]);
  100. }
  101. else
  102. {
  103. // Append to current path
  104. strcpy(absolute_path, shell_path);
  105. if (strlen(absolute_path) + strlen(shell_tokens[0]) < MAX_PATH_LENGTH)
  106. {
  107. // If not root, append slash
  108. if (strcmp(shell_path, "/") != 0)
  109. {
  110. strcat(absolute_path, "/");
  111. }
  112. strcat(absolute_path, shell_tokens[0]);
  113. }
  114. else
  115. {
  116. GFX_PrintConsole("Path too long\n");
  117. return 0;
  118. }
  119. }
  120. }
  121. // Get filesize of the program
  122. struct brfs_dir_entry* dir = brfs_stat(absolute_path);
  123. if ((word)dir == -1)
  124. {
  125. //GFX_PrintConsole("Program not found\n");
  126. return 0;
  127. }
  128. word filesize = dir->filesize;
  129. // Attempt to open file
  130. word fp = brfs_open_file(absolute_path);
  131. if (fp == -1)
  132. {
  133. // No warning as a local dir could have the same name as a program in SHELL_BIN_PATH
  134. return 0;
  135. }
  136. // Set cursor to start of file
  137. brfs_set_cursor(fp, 0);
  138. // Read program into memory
  139. word* program = (word*) RUN_ADDR;
  140. if (!brfs_read(fp, program, filesize))
  141. {
  142. GFX_PrintConsole("Could not read file\n");
  143. return 0;
  144. }
  145. // Run program
  146. // Indicate that a user program is running
  147. bdos_userprogram_running = 1;
  148. uprintln("Running program...");
  149. // jump to the program
  150. asm(
  151. "; backup registers\n"
  152. "push r1\n"
  153. "push r2\n"
  154. "push r3\n"
  155. "push r4\n"
  156. "push r5\n"
  157. "push r6\n"
  158. "push r7\n"
  159. "push r8\n"
  160. "push r9\n"
  161. "push r10\n"
  162. "push r11\n"
  163. "push r12\n"
  164. "push r13\n"
  165. "push r14\n"
  166. "push r15\n"
  167. //"ccache\n"
  168. "savpc r1\n"
  169. "push r1\n"
  170. "jump 0x400000\n"
  171. "; restore registers\n"
  172. "pop r15\n"
  173. "pop r14\n"
  174. "pop r13\n"
  175. "pop r12\n"
  176. "pop r11\n"
  177. "pop r10\n"
  178. "pop r9\n"
  179. "pop r8\n"
  180. "pop r7\n"
  181. "pop r6\n"
  182. "pop r5\n"
  183. "pop r4\n"
  184. "pop r3\n"
  185. "pop r2\n"
  186. "pop r1\n"
  187. );
  188. // Indicate that no user program is running anymore
  189. bdos_userprogram_running = 0;
  190. bdos_restore();
  191. // Close file
  192. brfs_close_file(fp);
  193. return 1;
  194. }
  195. /**
  196. * Process dots in path so that ".." goes up one directory and "." is skipped
  197. */
  198. void shell_process_dots(char* path)
  199. {
  200. // Create a copy of the path
  201. char path_copy[MAX_PATH_LENGTH];
  202. strcpy(path_copy, path);
  203. // Split path by '/' and traverse directories
  204. word num_tokens = 0;
  205. char* tokens[MAX_PATH_LENGTH >> 1];
  206. tokens[num_tokens] = strtok(path_copy+1, "/");
  207. while((word)tokens[num_tokens] != -1)
  208. {
  209. num_tokens++;
  210. tokens[num_tokens] = strtok(-1, "/");
  211. }
  212. if (num_tokens == 0)
  213. {
  214. return;
  215. }
  216. strcpy(path, "/");
  217. // Traverse path
  218. word i = 0;
  219. while (i < num_tokens)
  220. {
  221. if (strcmp(tokens[i], ".") == 0)
  222. {
  223. // Skip
  224. }
  225. else if (strcmp(tokens[i], "..") == 0)
  226. {
  227. // Remove all characters after last slash
  228. word j = strlen(path) - 1;
  229. while (j > 0 && path[j] != '/')
  230. {
  231. path[j] = 0;
  232. j--;
  233. }
  234. }
  235. else
  236. {
  237. // Copy token
  238. if (strlen(path) > 1)
  239. {
  240. strcat(path, "/");
  241. }
  242. strcat(path, tokens[i]);
  243. }
  244. i++;
  245. }
  246. }
  247. /**
  248. * Change directory
  249. */
  250. void shell_change_directory()
  251. {
  252. if (shell_num_tokens == 1)
  253. {
  254. // Set path to root if no argument
  255. strcpy(shell_path, "/");
  256. return;
  257. }
  258. // Remove trailing slashes, skipping the first character
  259. word i = strlen(shell_tokens[1]) - 1;
  260. while (i >= 1 && shell_tokens[1][i] == '/')
  261. {
  262. shell_tokens[1][i] = 0;
  263. i--;
  264. }
  265. // Check if path is root
  266. if (strcmp(shell_tokens[1], "/") == 0)
  267. {
  268. strcpy(shell_path, "/");
  269. return;
  270. }
  271. char absolute_path[MAX_PATH_LENGTH];
  272. // Create absolute path
  273. // Check if argument is relative or absolute
  274. if (shell_tokens[1][0] == '/')
  275. {
  276. strcpy(absolute_path, shell_tokens[1]);
  277. }
  278. else
  279. {
  280. // Append to current path
  281. strcpy(absolute_path, shell_path);
  282. if (strlen(absolute_path) + strlen(shell_tokens[1]) < MAX_PATH_LENGTH)
  283. {
  284. // If not root, append slash
  285. if (strcmp(shell_path, "/") != 0)
  286. {
  287. strcat(absolute_path, "/");
  288. }
  289. strcat(absolute_path, shell_tokens[1]);
  290. }
  291. else
  292. {
  293. GFX_PrintConsole("Path too long\n");
  294. }
  295. }
  296. // Get dir entry of the path
  297. struct brfs_dir_entry* dir = brfs_stat(absolute_path);
  298. // Check if valid and is a directory
  299. if ((word)dir != -1 && dir->flags & (1 << 0))
  300. {
  301. // Set new path
  302. strcpy(shell_path, absolute_path);
  303. // Process dots in path
  304. shell_process_dots(shell_path);
  305. }
  306. else
  307. {
  308. GFX_PrintConsole("Directory not found\n");
  309. }
  310. }
  311. /**
  312. * Format the filesystem
  313. */
  314. void shell_format_filesystem()
  315. {
  316. if (shell_num_tokens < 3)
  317. {
  318. GFX_PrintConsole("Usage: format <blk size> <blk cnt>\n");
  319. return;
  320. }
  321. word block_size = strToInt(shell_tokens[1]);
  322. word block_count = strToInt(shell_tokens[2]);
  323. word full_format = 1;
  324. brfs_format(block_count, block_size, "FPGC", full_format);
  325. brfs_write_to_flash();
  326. }
  327. /**
  328. * Show filesystem usage
  329. */
  330. void shell_show_fs_usage()
  331. {
  332. struct brfs_superblock* superblock = (struct brfs_superblock*)brfs_ram_storage;
  333. word total_blocks = superblock->total_blocks;
  334. word block_size = superblock->words_per_block;
  335. char* fat_addr = brfs_ram_storage + SUPERBLOCK_SIZE;
  336. // Loop through FAT table and count free blocks
  337. word free_blocks = 0;
  338. word i = 0;
  339. while (i < total_blocks)
  340. {
  341. if (fat_addr[i] == 0)
  342. {
  343. free_blocks++;
  344. }
  345. i++;
  346. }
  347. word used_blocks = total_blocks - free_blocks;
  348. word used_space = used_blocks * block_size;
  349. word free_space = free_blocks * block_size;
  350. word total_space = total_blocks * block_size;
  351. word percentage_used_space = MATH_divU(used_space*100, total_space);
  352. GFX_PrintConsole("Usage: ");
  353. GFX_PrintDecConsole(percentage_used_space);
  354. GFX_PrintConsole("%\n-----------\n");
  355. GFX_PrintConsole("Used space : ");
  356. GFX_PrintDecConsole(MATH_divU(used_space, 1000));
  357. GFX_PrintConsole("/");
  358. GFX_PrintDecConsole(MATH_divU(total_space, 1000));
  359. GFX_PrintConsole(" kwords\n");
  360. GFX_PrintConsole("Used blocks : ");
  361. GFX_PrintDecConsole(used_blocks);
  362. GFX_PrintConsole("/");
  363. GFX_PrintDecConsole(total_blocks);
  364. GFX_PrintConsole("\n\n");
  365. GFX_PrintConsole("Free space : ");
  366. GFX_PrintDecConsole(MATH_divU(free_space, 1000));
  367. GFX_PrintConsole(" kwords\n");
  368. GFX_PrintConsole("Free blocks : ");
  369. GFX_PrintDecConsole(free_blocks);
  370. GFX_PrintConsole("\n\n");
  371. GFX_PrintConsole("Block size : ");
  372. GFX_PrintDecConsole(block_size);
  373. GFX_PrintConsole(" words\n");
  374. GFX_PrintConsole("Total blocks: ");
  375. GFX_PrintDecConsole(total_blocks);
  376. GFX_PrintConsole("\n");
  377. }
  378. /**
  379. * Handle the command
  380. */
  381. void shell_handle_command()
  382. {
  383. if (strcmp(shell_tokens[0], "cd") == 0)
  384. {
  385. shell_change_directory();
  386. }
  387. else if (strcmp(shell_tokens[0], "clear") == 0)
  388. {
  389. // clear screen by clearing window tables and resetting the cursor
  390. GFX_clearWindowtileTable();
  391. GFX_clearWindowpaletteTable();
  392. GFX_cursor = 0;
  393. }
  394. else if (strcmp(shell_tokens[0], "help") == 0)
  395. {
  396. shell_print_help();
  397. }
  398. else if (strcmp(shell_tokens[0], "sync") == 0)
  399. {
  400. brfs_write_to_flash();
  401. }
  402. else if (strcmp(shell_tokens[0], "dump") == 0)
  403. {
  404. // Dump FAT
  405. brfs_dump_section(brfs_ram_storage+SUPERBLOCK_SIZE, strToInt(shell_tokens[1]), 16);
  406. }
  407. else if (strcmp(shell_tokens[0], "format") == 0)
  408. {
  409. shell_format_filesystem();
  410. }
  411. else if (strcmp(shell_tokens[0], "df") == 0)
  412. {
  413. shell_show_fs_usage();
  414. }
  415. // Attempt to run program both from local dir and from SHELL_BIN_PATH
  416. else if (!shell_run_program(0))
  417. {
  418. if (!shell_run_program(1))
  419. {
  420. GFX_PrintConsole("Command not found\n");
  421. }
  422. }
  423. }
  424. /**
  425. * Initialize shell
  426. */
  427. void shell_init()
  428. {
  429. // Clear screen by clearing window tables and resetting the cursor
  430. GFX_clearWindowtileTable();
  431. GFX_clearWindowpaletteTable();
  432. GFX_cursor = 0;
  433. shell_clear_command();
  434. // Set path to root
  435. strcpy(shell_path, "/");
  436. shell_print_prompt();
  437. }
  438. /**
  439. * Main shell loop
  440. */
  441. void shell_loop()
  442. {
  443. // Read command from HID FIFO
  444. if (HID_FifoAvailable())
  445. {
  446. char c = HID_FifoRead();
  447. if (c == '\n')
  448. {
  449. GFX_PrintcConsole('\n');
  450. shell_parse_command();
  451. if (shell_num_tokens != 0)
  452. {
  453. shell_handle_command();
  454. }
  455. shell_clear_command();
  456. shell_print_prompt();
  457. }
  458. else if (c == 0x8) // backspace
  459. {
  460. if (shell_cmd_idx > 0)
  461. {
  462. shell_cmd_idx--;
  463. shell_cmd[shell_cmd_idx] = 0;
  464. GFX_PrintcConsole(0x8);
  465. }
  466. }
  467. else
  468. {
  469. // Append character to command buffer and print it
  470. shell_append_command(c);
  471. GFX_PrintcConsole(c);
  472. }
  473. }
  474. }