ImageBackingStore.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /**
  2. * ZuluSCSI™ - Copyright (c) 2022-2023 Rabbit Hole Computing™
  3. * Portions - Copyright (C) 2023 Eric Helgeson
  4. *
  5. * This file is licensed under the GPL version 3 or any later version. 
  6. *
  7. * https://www.gnu.org/licenses/gpl-3.0.html
  8. * ----
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version. 
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. * GNU General Public License for more details. 
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  21. **/
  22. #include "ImageBackingStore.h"
  23. #include <SdFat.h>
  24. #include <ZuluSCSI_platform.h>
  25. #include "ZuluSCSI_log.h"
  26. #include "ZuluSCSI_config.h"
  27. #include "ZuluSCSI_settings.h"
  28. #include <minIni.h>
  29. #include <strings.h>
  30. #include <string.h>
  31. #include <assert.h>
  32. extern bool g_rawdrive_active;
  33. ImageBackingStore::ImageBackingStore()
  34. {
  35. m_iscontiguous = false;
  36. m_israw = false;
  37. g_rawdrive_active = m_israw;
  38. m_isrom = false;
  39. m_isreadonly_attr = false;
  40. m_blockdev = nullptr;
  41. m_bgnsector = m_endsector = m_cursector = 0;
  42. m_isfolder = false;
  43. m_foldername[0] = '\0';
  44. }
  45. ImageBackingStore::ImageBackingStore(const char *filename, uint32_t scsi_block_size): ImageBackingStore()
  46. {
  47. if (strncasecmp(filename, "RAW:", 4) == 0)
  48. {
  49. char *endptr, *endptr2;
  50. m_bgnsector = strtoul(filename + 4, &endptr, 0);
  51. m_endsector = strtoul(endptr + 1, &endptr2, 0);
  52. if (*endptr != ':' || *endptr2 != '\0')
  53. {
  54. logmsg("Invalid format for raw filename: ", filename);
  55. return;
  56. }
  57. if ((scsi_block_size % SD_SECTOR_SIZE) != 0)
  58. {
  59. logmsg("SCSI block size ", (int)scsi_block_size, " is not supported for RAW partitions (must be divisible by 512 bytes)");
  60. return;
  61. }
  62. m_iscontiguous = true;
  63. m_israw = true;
  64. g_rawdrive_active = m_israw;
  65. m_blockdev = SD.card();
  66. uint32_t sectorCount = SD.card()->sectorCount();
  67. if (m_endsector >= sectorCount)
  68. {
  69. logmsg("---- Limiting RAW image mapping to SD card sector count: ", (int)sectorCount);
  70. m_endsector = sectorCount - 1;
  71. }
  72. }
  73. else if (strncasecmp(filename, "ROM:", 4) == 0)
  74. {
  75. if (!romDriveCheckPresent(&m_romhdr))
  76. {
  77. m_romhdr.imagesize = 0;
  78. }
  79. else
  80. {
  81. m_isrom = true;
  82. }
  83. }
  84. else
  85. {
  86. if (SD.open(filename, O_RDONLY).isDir())
  87. {
  88. // Folder that contains .cue sheet and multiple .bin files
  89. m_isfolder = true;
  90. strncpy(m_foldername, filename, sizeof(m_foldername));
  91. m_foldername[sizeof(m_foldername)-1] = '\0';
  92. }
  93. else
  94. {
  95. // Regular image file
  96. _internal_open(filename);
  97. }
  98. }
  99. }
  100. bool ImageBackingStore::_internal_open(const char *filename)
  101. {
  102. m_isreadonly_attr = !!(FS_ATTRIB_READ_ONLY & SD.attrib(filename));
  103. oflag_t open_flag = O_RDWR;
  104. if (m_isreadonly_attr && !m_isfolder)
  105. {
  106. open_flag = O_RDONLY;
  107. logmsg("---- Image file is read-only, writes disabled");
  108. }
  109. if (m_isfolder)
  110. {
  111. char fullpath[MAX_FILE_PATH * 2];
  112. strncpy(fullpath, m_foldername, sizeof(fullpath) - strlen(fullpath));
  113. strncat(fullpath, "/", sizeof(fullpath) - strlen(fullpath));
  114. strncat(fullpath, filename, sizeof(fullpath) - strlen(fullpath));
  115. m_fsfile = SD.open(fullpath, open_flag);
  116. }
  117. else
  118. {
  119. m_fsfile = SD.open(filename, open_flag);
  120. }
  121. if (!m_fsfile.isOpen())
  122. {
  123. return false;
  124. }
  125. uint32_t sectorcount = m_fsfile.size() / SD_SECTOR_SIZE;
  126. uint32_t begin = 0, end = 0;
  127. if (m_fsfile.contiguousRange(&begin, &end) && end >= begin + sectorcount)
  128. {
  129. // Convert to raw mapping, this avoids some unnecessary
  130. // access overhead in SdFat library.
  131. // If non-aligned offsets are later requested, it automatically falls
  132. // back to SdFat access mode.
  133. m_iscontiguous = true;
  134. m_blockdev = SD.card();
  135. m_bgnsector = begin;
  136. if (end != begin + sectorcount)
  137. {
  138. uint32_t allocsize = end - begin + 1;
  139. // Due to issue #80 in ZuluSCSI version 1.0.8 and 1.0.9 the allocated size was mistakenly reported to SCSI controller.
  140. // If the drive was formatted using those versions, you may have problems accessing it with newer firmware.
  141. // The old behavior can be restored with setting [SCSI] UseFATAllocSize = 1 in config file.
  142. if (g_scsi_settings.getSystem()->useFATAllocSize)
  143. {
  144. sectorcount = allocsize;
  145. }
  146. }
  147. m_endsector = begin + sectorcount - 1;
  148. m_fsfile.flush(); // Note: m_fsfile is also kept open as a fallback.
  149. }
  150. return true;
  151. }
  152. bool ImageBackingStore::isOpen()
  153. {
  154. if (m_iscontiguous)
  155. return (m_blockdev != NULL);
  156. else if (m_isrom)
  157. return (m_romhdr.imagesize > 0);
  158. else if (m_isfolder)
  159. return m_foldername[0] != '\0';
  160. else
  161. return m_fsfile.isOpen();
  162. }
  163. bool ImageBackingStore::isWritable()
  164. {
  165. return !m_isrom && !m_isreadonly_attr;
  166. }
  167. bool ImageBackingStore::isRaw()
  168. {
  169. return m_israw;
  170. }
  171. bool ImageBackingStore::isRom()
  172. {
  173. return m_isrom;
  174. }
  175. bool ImageBackingStore::isFolder()
  176. {
  177. return m_isfolder;
  178. }
  179. bool ImageBackingStore::isContiguous()
  180. {
  181. return m_iscontiguous;
  182. }
  183. bool ImageBackingStore::close()
  184. {
  185. m_isfolder = false;
  186. if (m_iscontiguous)
  187. {
  188. m_blockdev = nullptr;
  189. return true;
  190. }
  191. else if (m_isrom)
  192. {
  193. m_romhdr.imagesize = 0;
  194. return true;
  195. }
  196. else
  197. {
  198. return m_fsfile.close();
  199. }
  200. }
  201. uint64_t ImageBackingStore::size()
  202. {
  203. if (m_iscontiguous && m_blockdev && m_israw)
  204. {
  205. return (uint64_t)(m_endsector - m_bgnsector + 1) * SD_SECTOR_SIZE;
  206. }
  207. else if (m_isrom)
  208. {
  209. return m_romhdr.imagesize;
  210. }
  211. else
  212. {
  213. return m_fsfile.size();
  214. }
  215. }
  216. bool ImageBackingStore::contiguousRange(uint32_t* bgnSector, uint32_t* endSector)
  217. {
  218. if (m_iscontiguous && m_blockdev)
  219. {
  220. *bgnSector = m_bgnsector;
  221. *endSector = m_endsector;
  222. return true;
  223. }
  224. else if (m_isrom)
  225. {
  226. *bgnSector = 0;
  227. *endSector = 0;
  228. return true;
  229. }
  230. else
  231. {
  232. return m_fsfile.contiguousRange(bgnSector, endSector);
  233. }
  234. }
  235. bool ImageBackingStore::seek(uint64_t pos)
  236. {
  237. uint32_t sectornum = pos / SD_SECTOR_SIZE;
  238. if (m_iscontiguous && (uint64_t)sectornum * SD_SECTOR_SIZE != pos)
  239. {
  240. dbgmsg("---- Unaligned access to image, falling back to SdFat access mode");
  241. m_iscontiguous = false;
  242. }
  243. if (m_iscontiguous)
  244. {
  245. m_cursector = m_bgnsector + sectornum;
  246. return (m_cursector <= m_endsector);
  247. }
  248. else if (m_isrom)
  249. {
  250. uint32_t sectornum = pos / SD_SECTOR_SIZE;
  251. assert((uint64_t)sectornum * SD_SECTOR_SIZE == pos);
  252. m_cursector = sectornum;
  253. return m_cursector * SD_SECTOR_SIZE < m_romhdr.imagesize;
  254. }
  255. else
  256. {
  257. return m_fsfile.seek(pos);
  258. }
  259. }
  260. ssize_t ImageBackingStore::read(void* buf, size_t count)
  261. {
  262. uint32_t sectorcount = count / SD_SECTOR_SIZE;
  263. if (m_iscontiguous && (uint64_t)sectorcount * SD_SECTOR_SIZE != count)
  264. {
  265. dbgmsg("---- Unaligned access to image, falling back to SdFat access mode");
  266. m_iscontiguous = false;
  267. }
  268. if (m_iscontiguous && m_blockdev)
  269. {
  270. if (m_blockdev->readSectors(m_cursector, (uint8_t*)buf, sectorcount))
  271. {
  272. m_cursector += sectorcount;
  273. return count;
  274. }
  275. else
  276. {
  277. return -1;
  278. }
  279. }
  280. else if (m_isrom)
  281. {
  282. uint32_t sectorcount = count / SD_SECTOR_SIZE;
  283. assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count);
  284. uint32_t start = m_cursector * SD_SECTOR_SIZE;
  285. if (romDriveRead((uint8_t*)buf, start, count))
  286. {
  287. m_cursector += sectorcount;
  288. return count;
  289. }
  290. else
  291. {
  292. return -1;
  293. }
  294. }
  295. else
  296. {
  297. return m_fsfile.read(buf, count);
  298. }
  299. }
  300. ssize_t ImageBackingStore::write(const void* buf, size_t count)
  301. {
  302. uint32_t sectorcount = count / SD_SECTOR_SIZE;
  303. if (m_iscontiguous && (uint64_t)sectorcount * SD_SECTOR_SIZE != count)
  304. {
  305. dbgmsg("---- Unaligned access to image, falling back to SdFat access mode");
  306. m_iscontiguous = false;
  307. }
  308. if (m_iscontiguous && m_blockdev)
  309. {
  310. if (m_blockdev->writeSectors(m_cursector, (const uint8_t*)buf, sectorcount))
  311. {
  312. m_cursector += sectorcount;
  313. return count;
  314. }
  315. else
  316. {
  317. return 0;
  318. }
  319. }
  320. else if (m_isrom)
  321. {
  322. logmsg("ERROR: attempted to write to ROM drive");
  323. return 0;
  324. }
  325. else if (m_isreadonly_attr)
  326. {
  327. logmsg("ERROR: attempted to write to a read only image");
  328. return 0;
  329. }
  330. else
  331. {
  332. return m_fsfile.write(buf, count);
  333. }
  334. }
  335. void ImageBackingStore::flush()
  336. {
  337. if (!m_iscontiguous && !m_isrom && !m_isreadonly_attr)
  338. {
  339. m_fsfile.flush();
  340. }
  341. }
  342. uint64_t ImageBackingStore::position()
  343. {
  344. if (!m_iscontiguous && !m_isrom)
  345. {
  346. return m_fsfile.curPosition();
  347. }
  348. else
  349. {
  350. return 0;
  351. }
  352. }
  353. size_t ImageBackingStore::getFilename(char* buf, size_t buflen)
  354. {
  355. if (m_fsfile.isOpen())
  356. {
  357. size_t name_length = m_fsfile.getName(buf, buflen);
  358. if (name_length + 1 > buflen)
  359. return 0;
  360. else
  361. return name_length;
  362. }
  363. return 0;
  364. }
  365. bool ImageBackingStore::selectImageFile(const char *filename)
  366. {
  367. if (!m_isfolder)
  368. {
  369. logmsg("Attempted selectImageFile() but image is not a folder");
  370. return false;
  371. }
  372. return _internal_open(filename);
  373. }
  374. size_t ImageBackingStore::getFoldername(char* buf, size_t buflen)
  375. {
  376. if (m_isfolder)
  377. {
  378. size_t name_length = strlen(m_foldername);
  379. if (name_length + 1 > buflen)
  380. return 0;
  381. strncpy(buf, m_foldername, buflen);
  382. return name_length;
  383. }
  384. return 0;
  385. }