BlueSCSI_Toolbox.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. /**
  2. * Copyright (C) 2023 Eric Helgeson
  3. *
  4. * This file is part of BlueSCSI
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. **/
  19. #include "BlueSCSI_Toolbox.h"
  20. #include "BlueSCSI_disk.h"
  21. #include "BlueSCSI_cdrom.h"
  22. #include "BlueSCSI_log.h"
  23. #include <minIni.h>
  24. #include <SdFat.h>
  25. extern "C" {
  26. #include <bluescsi_toolbox.h>
  27. #include <scsi2sd_time.h>
  28. #include <sd.h>
  29. #include <mode.h>
  30. }
  31. extern "C" int8_t scsiToolboxEnabled()
  32. {
  33. static int8_t enabled = -1;
  34. if (enabled == -1)
  35. {
  36. enabled = ini_getbool("SCSI", "EnableToolbox", 1, CONFIGFILE);
  37. dbgmsg("BlueSCSI Toolbox enabled = ", enabled);
  38. }
  39. return enabled == 1;
  40. }
  41. static bool toolboxFilenameValid(const char* name, const bool isCD = false)
  42. {
  43. if(strlen(name) == 0)
  44. {
  45. dbgmsg("toolbox: Ignoring filename empty file name");
  46. return false;
  47. }
  48. if(name[0] == '.')
  49. {
  50. dbgmsg("toolbox: Ignoring hidden file ", name);
  51. return false;
  52. }
  53. if (isCD)
  54. {
  55. return scsiDiskFilenameValid(name);
  56. }
  57. return true;
  58. }
  59. static void doCountFiles(const char * dir_name, bool isCD = false)
  60. {
  61. FsFile dir;
  62. FsFile file;
  63. char name[MAX_FILE_PATH] = {0};
  64. dir.open(dir_name);
  65. dir.rewindDirectory();
  66. uint8_t file_count = 0;
  67. while (file.openNext(&dir, O_RDONLY))
  68. {
  69. if(file.getError() > 0)
  70. {
  71. file.close();
  72. break;
  73. }
  74. bool isDir = file.isDirectory();
  75. size_t len = file.getName(name, MAX_FILE_PATH);
  76. file.close();
  77. if (isCD && isDir)
  78. continue;
  79. // truncate filename the same way listing does, before validating name
  80. if (len > MAX_MAC_PATH)
  81. name[MAX_MAC_PATH] = 0x0;
  82. dbgmsg("TOOLBOX COUNT FILES: truncated filename is '", name, "'");
  83. // only count valid files.
  84. if(toolboxFilenameValid(name, isCD))
  85. {
  86. file_count = file_count + 1;
  87. if(file_count > MAX_FILE_LISTING_FILES) {
  88. scsiDev.status = CHECK_CONDITION;
  89. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  90. scsiDev.target->sense.asc = OPEN_RETRO_SCSI_TOO_MANY_FILES;
  91. scsiDev.phase = STATUS;
  92. dir.close();
  93. return;
  94. }
  95. }
  96. }
  97. scsiDev.data[0] = file_count;
  98. scsiDev.dataLen = sizeof(file_count);
  99. scsiDev.phase = DATA_IN;
  100. }
  101. static void onListFiles(const char * dir_name, bool isCD = false) {
  102. FsFile dir;
  103. FsFile file;
  104. memset(scsiDev.data, 0, ENTRY_SIZE * (MAX_FILE_LISTING_FILES + 1));
  105. char name[MAX_FILE_PATH] = {0};
  106. uint8_t index = 0;
  107. uint8_t file_entry[ENTRY_SIZE] = {0};
  108. if (!dir.open(dir_name)) {
  109. if (!SD.mkdir(dir_name) || !dir.open(dir_name)) {
  110. logmsg("ERROR: Could not open or create BlueSCSI Toolbox shared dir: ", dir_name);
  111. }
  112. }
  113. dir.rewindDirectory();
  114. while (file.openNext(&dir, O_RDONLY))
  115. {
  116. memset(name, 0, sizeof(name));
  117. // get base information
  118. uint8_t isDir = file.isDirectory() ? 0x00 : 0x01;
  119. size_t len = file.getName(name, MAX_FILE_PATH);
  120. uint64_t size = file.fileSize();
  121. file.close();
  122. // validate file is allowed for this listing
  123. if (!toolboxFilenameValid(name, isCD))
  124. continue;
  125. if (isCD && isDir == 0x00)
  126. continue;
  127. // truncate filename to fit in destination buffer
  128. if (len > MAX_MAC_PATH)
  129. name[MAX_MAC_PATH] = 0x0;
  130. dbgmsg("TOOLBOX LIST FILES: truncated filename is '", name, "'");
  131. // fill output buffer
  132. file_entry[0] = index;
  133. file_entry[1] = isDir;
  134. for(int i = 0; i < MAX_MAC_PATH + 1 ; i++) {
  135. file_entry[i + 2] = name[i]; // bytes 2 - 34
  136. }
  137. file_entry[35] = 0; //(size >> 32) & 0xff;
  138. file_entry[36] = (size >> 24) & 0xff;
  139. file_entry[37] = (size >> 16) & 0xff;
  140. file_entry[38] = (size >> 8) & 0xff;
  141. file_entry[39] = (size) & 0xff;
  142. // send to SCSI output buffer
  143. memcpy(&(scsiDev.data[ENTRY_SIZE * index]), file_entry, ENTRY_SIZE);
  144. // increment index
  145. index = index + 1;
  146. if (index >= MAX_FILE_LISTING_FILES) break;
  147. }
  148. dir.close();
  149. scsiDev.dataLen = ENTRY_SIZE * index;
  150. scsiDev.phase = DATA_IN;
  151. dbgmsg("TOOLBOX LIST FILES: returning ", index, " files for size ", scsiDev.dataLen);
  152. }
  153. static FsFile get_file_from_index(uint8_t index, const char * dir_name, bool isCD = false)
  154. {
  155. FsFile dir;
  156. FsFile file_test;
  157. char name[MAX_FILE_PATH] = {0};
  158. dir.open(dir_name);
  159. dir.rewindDirectory(); // Back to the top
  160. int count = 0;
  161. while (file_test.openNext(&dir, O_RDONLY))
  162. {
  163. // If error there is no next file to open.
  164. if(file_test.getError() > 0) {
  165. file_test.close();
  166. break;
  167. }
  168. // no directories in CD image listing
  169. if (isCD && file_test.isDirectory())
  170. {
  171. file_test.close();
  172. continue;
  173. }
  174. // truncate filename the same way listing does, before validating name
  175. size_t len = file_test.getName(name, MAX_FILE_PATH);
  176. if (len > MAX_MAC_PATH)
  177. name[MAX_MAC_PATH] = 0x0;
  178. // validate filename
  179. if(!toolboxFilenameValid(name, isCD))
  180. {
  181. file_test.close();
  182. continue;
  183. }
  184. // found file?
  185. if (count == index)
  186. {
  187. dir.close();
  188. return file_test;
  189. }
  190. else
  191. {
  192. file_test.close();
  193. }
  194. count++;
  195. }
  196. file_test.close();
  197. dir.close();
  198. return file_test;
  199. }
  200. // Devices that are active on this SCSI device.
  201. static void onListDevices()
  202. {
  203. for (int i = 0; i < NUM_SCSIID; i++)
  204. {
  205. const S2S_TargetCfg* cfg = s2s_getConfigById(i);
  206. if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
  207. {
  208. scsiDev.data[i] = static_cast<int>(cfg->deviceType); // 2 == cd
  209. }
  210. else
  211. {
  212. scsiDev.data[i] = 0xFF; // not enabled target.
  213. }
  214. }
  215. scsiDev.dataLen = NUM_SCSIID;
  216. scsiDev.phase = DATA_IN;
  217. }
  218. static void onSetNextCD(const char * img_dir)
  219. {
  220. char name[MAX_FILE_PATH] = {0};
  221. char full_path[MAX_FILE_PATH * 2] = {0};
  222. uint8_t file_index = scsiDev.cdb[1];
  223. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  224. FsFile next_cd = get_file_from_index(file_index, img_dir, true);
  225. next_cd.getName(name, sizeof(name));
  226. next_cd.close();
  227. snprintf(full_path, (MAX_FILE_PATH * 2), "%s/%s", img_dir, name);
  228. switchNextImage(img, full_path);
  229. }
  230. FsFile gFile; // global so we can keep it open while transferring.
  231. void onGetFile10(char * dir_name) {
  232. uint8_t index = scsiDev.cdb[1];
  233. uint32_t offset = ((uint32_t)scsiDev.cdb[2] << 24) | ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
  234. if (offset == 0) // first time, open the file.
  235. {
  236. gFile = get_file_from_index(index, dir_name);
  237. if(!gFile.isDirectory() && !gFile.isReadable())
  238. {
  239. scsiDev.status = CHECK_CONDITION;
  240. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  241. //SCSI_ASC_INVALID_FIELD_IN_CDB
  242. scsiDev.phase = STATUS;
  243. return;
  244. }
  245. }
  246. uint32_t file_total = gFile.size();
  247. memset(scsiDev.data, 0, 4096);
  248. gFile.seekSet(offset * 4096);
  249. int bytes_read = gFile.read(scsiDev.data, 4096);
  250. if(offset * 4096 >= file_total) // transfer done, close.
  251. {
  252. gFile.close();
  253. }
  254. scsiDev.dataLen = bytes_read;
  255. scsiDev.phase = DATA_IN;
  256. }
  257. /*
  258. Prepares a file for receiving. The file name is null terminated in the scsi data.
  259. */
  260. static void onSendFilePrep(char * dir_name)
  261. {
  262. char file_name[32+1];
  263. scsiEnterPhase(DATA_OUT);
  264. scsiRead(static_cast<uint8_t *>(static_cast<void *>(file_name)), 32+1, NULL);
  265. file_name[32] = '\0';
  266. dbgmsg("TOOLBOX OPEN FILE FOR WRITE: '", file_name, "'");
  267. SD.chdir(dir_name);
  268. gFile.open(file_name, FILE_WRITE);
  269. SD.chdir("/");
  270. if(gFile.isOpen() && gFile.isWritable())
  271. {
  272. gFile.rewind();
  273. gFile.sync();
  274. // do i need to manually set phase to status here?
  275. return;
  276. } else {
  277. gFile.close();
  278. scsiDev.status = CHECK_CONDITION;
  279. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  280. //SCSI_ASC_INVALID_FIELD_IN_CDB
  281. scsiDev.phase = STATUS;
  282. }
  283. }
  284. static void onSendFileEnd(void)
  285. {
  286. gFile.sync();
  287. gFile.close();
  288. scsiDev.phase = STATUS;
  289. }
  290. static void onSendFile10(void)
  291. {
  292. if(!gFile.isOpen() || !gFile.isWritable())
  293. {
  294. scsiDev.status = CHECK_CONDITION;
  295. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  296. //SCSI_ASC_INVALID_FIELD_IN_CDB
  297. scsiDev.phase = STATUS;
  298. }
  299. // Number of bytes sent this request, 1..512.
  300. uint16_t bytes_sent = ((uint16_t)scsiDev.cdb[1] << 8) | scsiDev.cdb[2];
  301. // 512 byte offset of where to put these bytes.
  302. uint32_t offset = ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
  303. const uint16_t BUFSIZE = 512;
  304. uint8_t buf[BUFSIZE];
  305. // Do not allow buffer overrun
  306. if (bytes_sent > BUFSIZE)
  307. {
  308. dbgmsg("TOOLBOX SEND FILE 10 ILLEGAL DATA SIZE");
  309. gFile.close();
  310. scsiDev.status = CHECK_CONDITION;
  311. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  312. }
  313. scsiEnterPhase(DATA_OUT);
  314. scsiRead(buf, bytes_sent, NULL);
  315. gFile.seekCur(offset * 512);
  316. gFile.write(buf, bytes_sent);
  317. if(gFile.getWriteError())
  318. {
  319. gFile.clearWriteError();
  320. gFile.close();
  321. scsiDev.status = CHECK_CONDITION;
  322. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  323. }
  324. //scsiDev.phase = STATUS;
  325. }
  326. static void onToggleDebug()
  327. {
  328. if(scsiDev.cdb[1] == 0) // 0 == Set Debug, 1 == Get Debug State
  329. {
  330. g_log_debug = scsiDev.cdb[2];
  331. logmsg("Set debug logs to: ", g_log_debug);
  332. scsiDev.phase = STATUS;
  333. }
  334. else
  335. {
  336. logmsg("Debug currently set to: ", g_log_debug);
  337. scsiDev.data[0] = g_log_debug ? 0x1 : 0x0;
  338. scsiDev.dataLen = 1;
  339. scsiDev.phase = DATA_IN;
  340. }
  341. }
  342. static int getToolBoxSharedDir(char * dir_name)
  343. {
  344. return ini_gets("SCSI", "ToolBoxSharedDir", "/shared", dir_name, MAX_FILE_PATH, CONFIGFILE);
  345. }
  346. extern "C" int scsiToolboxCommand()
  347. {
  348. int commandHandled = 1;
  349. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  350. uint8_t command = scsiDev.cdb[0];
  351. if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_FILES))
  352. {
  353. char img_dir[MAX_FILE_PATH];
  354. dbgmsg("BLUESCSI_TOOLBOX_COUNT_FILES");
  355. getToolBoxSharedDir(img_dir);
  356. doCountFiles(img_dir);
  357. }
  358. else if (unlikely(command == BLUESCSI_TOOLBOX_LIST_FILES))
  359. {
  360. char img_dir[MAX_FILE_PATH];
  361. dbgmsg("BLUESCSI_TOOLBOX_LIST_FILES");
  362. getToolBoxSharedDir(img_dir);
  363. onListFiles(img_dir);
  364. }
  365. else if (unlikely(command == BLUESCSI_TOOLBOX_GET_FILE))
  366. {
  367. char img_dir[MAX_FILE_PATH];
  368. dbgmsg("BLUESCSI_TOOLBOX_GET_FILE");
  369. getToolBoxSharedDir(img_dir);
  370. onGetFile10(img_dir);
  371. }
  372. else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_PREP))
  373. {
  374. char img_dir[MAX_FILE_PATH];
  375. dbgmsg("BLUESCSI_TOOLBOX_SEND_FILE_PREP");
  376. getToolBoxSharedDir(img_dir);
  377. onSendFilePrep(img_dir);
  378. }
  379. else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_10))
  380. {
  381. dbgmsg("BLUESCSI_TOOLBOX_SEND_FILE_10");
  382. onSendFile10();
  383. }
  384. else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_END))
  385. {
  386. dbgmsg("BLUESCSI_TOOLBOX_SEND_FILE_END");
  387. onSendFileEnd();
  388. }
  389. else if(unlikely(command == BLUESCSI_TOOLBOX_TOGGLE_DEBUG))
  390. {
  391. dbgmsg("BLUESCSI_TOOLBOX_TOGGLE_DEBUG");
  392. onToggleDebug();
  393. }
  394. else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_CDS))
  395. {
  396. char img_dir[4];
  397. dbgmsg("BLUESCSI_TOOLBOX_LIST_CDS");
  398. snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, static_cast<int>(img.scsiId) & S2S_CFG_TARGET_ID_BITS);
  399. onListFiles(img_dir, true);
  400. }
  401. else if(unlikely(command == BLUESCSI_TOOLBOX_SET_NEXT_CD))
  402. {
  403. char img_dir[4];
  404. dbgmsg("BLUESCSI_TOOLBOX_SET_NEXT_CD");
  405. snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, static_cast<int>(img.scsiId) & S2S_CFG_TARGET_ID_BITS);
  406. onSetNextCD(img_dir);
  407. }
  408. else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_DEVICES))
  409. {
  410. dbgmsg("BLUESCSI_TOOLBOX_LIST_DEVICES");
  411. onListDevices();
  412. }
  413. else if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_CDS))
  414. {
  415. char img_dir[4];
  416. dbgmsg("BLUESCSI_TOOLBOX_COUNT_CDS");
  417. snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, static_cast<int>(img.scsiId) & S2S_CFG_TARGET_ID_BITS);
  418. doCountFiles(img_dir, true);
  419. }
  420. else
  421. {
  422. commandHandled = 0;
  423. }
  424. return commandHandled;
  425. }