/** * ZuluSCSI™ - Copyright (c) 2022-2023 Rabbit Hole Computing™ * Portions - Copyright (C) 2023 Eric Helgeson * * This file is licensed under the GPL version 3 or any later version.  * * https://www.gnu.org/licenses/gpl-3.0.html * ---- * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version.  * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details.  * * You should have received a copy of the GNU General Public License * along with this program.  If not, see . **/ #include "ImageBackingStore.h" #include #include #include "ZuluSCSI_log.h" #include "ZuluSCSI_config.h" #include "ZuluSCSI_settings.h" #include #include #include #include extern bool g_rawdrive_active; ImageBackingStore::ImageBackingStore() { m_iscontiguous = false; m_israw = false; g_rawdrive_active = m_israw; m_isrom = false; m_isreadonly_attr = false; m_blockdev = nullptr; m_bgnsector = m_endsector = m_cursector = 0; m_isfolder = false; m_foldername[0] = '\0'; } ImageBackingStore::ImageBackingStore(const char *filename, uint32_t scsi_block_size): ImageBackingStore() { if (strncasecmp(filename, "RAW:", 4) == 0) { char *endptr, *endptr2; m_bgnsector = strtoul(filename + 4, &endptr, 0); m_endsector = strtoul(endptr + 1, &endptr2, 0); if (*endptr != ':' || *endptr2 != '\0') { logmsg("Invalid format for raw filename: ", filename); return; } if ((scsi_block_size % SD_SECTOR_SIZE) != 0) { logmsg("SCSI block size ", (int)scsi_block_size, " is not supported for RAW partitions (must be divisible by 512 bytes)"); return; } m_iscontiguous = true; m_israw = true; g_rawdrive_active = m_israw; m_blockdev = SD.card(); uint32_t sectorCount = SD.card()->sectorCount(); if (m_endsector >= sectorCount) { logmsg("---- Limiting RAW image mapping to SD card sector count: ", (int)sectorCount); m_endsector = sectorCount - 1; } } else if (strncasecmp(filename, "ROM:", 4) == 0) { if (!romDriveCheckPresent(&m_romhdr)) { m_romhdr.imagesize = 0; } else { m_isrom = true; } } else { if (SD.open(filename, O_RDONLY).isDir()) { // Folder that contains .cue sheet and multiple .bin files m_isfolder = true; strncpy(m_foldername, filename, sizeof(m_foldername)); m_foldername[sizeof(m_foldername)-1] = '\0'; } else { // Regular image file _internal_open(filename); } } } bool ImageBackingStore::_internal_open(const char *filename) { m_isreadonly_attr = !!(FS_ATTRIB_READ_ONLY & SD.attrib(filename)); oflag_t open_flag = O_RDWR; if (m_isreadonly_attr && !m_isfolder) { open_flag = O_RDONLY; logmsg("---- Image file is read-only, writes disabled"); } if (m_isfolder) { char fullpath[MAX_FILE_PATH * 2]; strncpy(fullpath, m_foldername, sizeof(fullpath) - strlen(fullpath)); strncat(fullpath, "/", sizeof(fullpath) - strlen(fullpath)); strncat(fullpath, filename, sizeof(fullpath) - strlen(fullpath)); m_fsfile = SD.open(fullpath, open_flag); } else { m_fsfile = SD.open(filename, open_flag); } if (!m_fsfile.isOpen()) { return false; } uint32_t sectorcount = m_fsfile.size() / SD_SECTOR_SIZE; uint32_t begin = 0, end = 0; if (m_fsfile.contiguousRange(&begin, &end) && end >= begin + sectorcount) { // Convert to raw mapping, this avoids some unnecessary // access overhead in SdFat library. // If non-aligned offsets are later requested, it automatically falls // back to SdFat access mode. m_iscontiguous = true; m_blockdev = SD.card(); m_bgnsector = begin; if (end != begin + sectorcount) { uint32_t allocsize = end - begin + 1; // Due to issue #80 in ZuluSCSI version 1.0.8 and 1.0.9 the allocated size was mistakenly reported to SCSI controller. // If the drive was formatted using those versions, you may have problems accessing it with newer firmware. // The old behavior can be restored with setting [SCSI] UseFATAllocSize = 1 in config file. if (g_scsi_settings.getSystem()->useFATAllocSize) { sectorcount = allocsize; } } m_endsector = begin + sectorcount - 1; m_fsfile.flush(); // Note: m_fsfile is also kept open as a fallback. } return true; } bool ImageBackingStore::isOpen() { if (m_iscontiguous) return (m_blockdev != NULL); else if (m_isrom) return (m_romhdr.imagesize > 0); else if (m_isfolder) return m_foldername[0] != '\0'; else return m_fsfile.isOpen(); } bool ImageBackingStore::isWritable() { return !m_isrom && !m_isreadonly_attr; } bool ImageBackingStore::isRaw() { return m_israw; } bool ImageBackingStore::isRom() { return m_isrom; } bool ImageBackingStore::isFolder() { return m_isfolder; } bool ImageBackingStore::isContiguous() { return m_iscontiguous; } bool ImageBackingStore::close() { m_isfolder = false; if (m_iscontiguous) { m_blockdev = nullptr; return true; } else if (m_isrom) { m_romhdr.imagesize = 0; return true; } else { return m_fsfile.close(); } } uint64_t ImageBackingStore::size() { if (m_iscontiguous && m_blockdev && m_israw) { return (uint64_t)(m_endsector - m_bgnsector + 1) * SD_SECTOR_SIZE; } else if (m_isrom) { return m_romhdr.imagesize; } else { return m_fsfile.size(); } } bool ImageBackingStore::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) { if (m_iscontiguous && m_blockdev) { *bgnSector = m_bgnsector; *endSector = m_endsector; return true; } else if (m_isrom) { *bgnSector = 0; *endSector = 0; return true; } else { return m_fsfile.contiguousRange(bgnSector, endSector); } } bool ImageBackingStore::seek(uint64_t pos) { uint32_t sectornum = pos / SD_SECTOR_SIZE; if (m_iscontiguous && (uint64_t)sectornum * SD_SECTOR_SIZE != pos) { dbgmsg("---- Unaligned access to image, falling back to SdFat access mode"); m_iscontiguous = false; } if (m_iscontiguous) { m_cursector = m_bgnsector + sectornum; return (m_cursector <= m_endsector); } else if (m_isrom) { uint32_t sectornum = pos / SD_SECTOR_SIZE; assert((uint64_t)sectornum * SD_SECTOR_SIZE == pos); m_cursector = sectornum; return m_cursector * SD_SECTOR_SIZE < m_romhdr.imagesize; } else { return m_fsfile.seek(pos); } } ssize_t ImageBackingStore::read(void* buf, size_t count) { uint32_t sectorcount = count / SD_SECTOR_SIZE; if (m_iscontiguous && (uint64_t)sectorcount * SD_SECTOR_SIZE != count) { dbgmsg("---- Unaligned access to image, falling back to SdFat access mode"); m_iscontiguous = false; } if (m_iscontiguous && m_blockdev) { if (m_blockdev->readSectors(m_cursector, (uint8_t*)buf, sectorcount)) { m_cursector += sectorcount; return count; } else { return -1; } } else if (m_isrom) { uint32_t sectorcount = count / SD_SECTOR_SIZE; assert((uint64_t)sectorcount * SD_SECTOR_SIZE == count); uint32_t start = m_cursector * SD_SECTOR_SIZE; if (romDriveRead((uint8_t*)buf, start, count)) { m_cursector += sectorcount; return count; } else { return -1; } } else { return m_fsfile.read(buf, count); } } ssize_t ImageBackingStore::write(const void* buf, size_t count) { uint32_t sectorcount = count / SD_SECTOR_SIZE; if (m_iscontiguous && (uint64_t)sectorcount * SD_SECTOR_SIZE != count) { dbgmsg("---- Unaligned access to image, falling back to SdFat access mode"); m_iscontiguous = false; } if (m_iscontiguous && m_blockdev) { if (m_blockdev->writeSectors(m_cursector, (const uint8_t*)buf, sectorcount)) { m_cursector += sectorcount; return count; } else { return 0; } } else if (m_isrom) { logmsg("ERROR: attempted to write to ROM drive"); return 0; } else if (m_isreadonly_attr) { logmsg("ERROR: attempted to write to a read only image"); return 0; } else { return m_fsfile.write(buf, count); } } void ImageBackingStore::flush() { if (!m_iscontiguous && !m_isrom && !m_isreadonly_attr) { m_fsfile.flush(); } } uint64_t ImageBackingStore::position() { if (!m_iscontiguous && !m_isrom) { return m_fsfile.curPosition(); } else { return 0; } } size_t ImageBackingStore::getFilename(char* buf, size_t buflen) { if (m_fsfile.isOpen()) { size_t name_length = m_fsfile.getName(buf, buflen); if (name_length + 1 > buflen) return 0; else return name_length; } return 0; } bool ImageBackingStore::selectImageFile(const char *filename) { if (!m_isfolder) { logmsg("Attempted selectImageFile() but image is not a folder"); return false; } return _internal_open(filename); } size_t ImageBackingStore::getFoldername(char* buf, size_t buflen) { if (m_isfolder) { size_t name_length = strlen(m_foldername); if (name_length + 1 > buflen) return 0; strncpy(buf, m_foldername, buflen); return name_length; } return 0; }