diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b86c3a..df5bf53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ set(CMAKE_AUTORCC ON) # Adding headers explicity so they are displayed in Qt Creator set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h drivelistmodelpollthread.h driveformatthread.h powersaveblocker.h cli.h + devicewrapper.h devicewrapperblockcacheentry.h devicewrapperpartition.h devicewrapperstructs.h devicewrapperfatpartition.h downloadthread.h downloadextractthread.h localfileextractthread.h downloadstatstelemetry.h dependencies/mountutils/src/mountutils.hpp dependencies/sha256crypt/sha256crypt.h) # Add dependencies @@ -88,9 +89,16 @@ if (NOT atomicbuiltin) endif() endif() +include(TestBigEndian) +test_big_endian(IS_BIG_ENDIAN) +if( IS_BIG_ENDIAN ) + message( FATAL_ERROR "We currently only support 'little endian' CPU architectures" ) +endif( IS_BIG_ENDIAN ) + set(SOURCES "main.cpp" "imagewriter.cpp" "networkaccessmanagerfactory.cpp" "drivelistitem.cpp" "drivelistmodel.cpp" "drivelistmodelpollthread.cpp" "downloadthread.cpp" "downloadextractthread.cpp" - "driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc" "dependencies/sha256crypt/sha256crypt.c" "cli.cpp") + "devicewrapper.cpp" "devicewrapperblockcacheentry.cpp" "devicewrapperpartition.cpp" "devicewrapperfatpartition.cpp" + "driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc" "dependencies/sha256crypt/sha256crypt.c" "cli.cpp") find_package(Qt5 COMPONENTS Core Quick LinguistTools Svg OPTIONAL_COMPONENTS Widgets) if (Qt5Widgets_FOUND) diff --git a/src/devicewrapper.cpp b/src/devicewrapper.cpp new file mode 100644 index 0000000..988b6b9 --- /dev/null +++ b/src/devicewrapper.cpp @@ -0,0 +1,175 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include "devicewrapper.h" +#include "devicewrapperblockcacheentry.h" +#include "devicewrapperstructs.h" +#include "devicewrapperfatpartition.h" +#include + +DeviceWrapper::DeviceWrapper(DeviceWrapperFile *file, QObject *parent) + : QObject(parent), _dirty(false), _file(file) +{ + +} + +DeviceWrapper::~DeviceWrapper() +{ + sync(); +} + +void DeviceWrapper::_seekToBlock(quint64 blockNr) +{ + if (!_file->seek(blockNr*4096)) + { + throw std::runtime_error("Error seeking device"); + } +} + +void DeviceWrapper::sync() +{ + if (!_dirty) + return; + + const auto blockNrs = _blockcache.keys(); + for (auto blockNr : blockNrs) + { + if (blockNr == 0) + continue; /* Save writing first block with MBR for last */ + + auto block = _blockcache.value(blockNr); + + if (!block->dirty) + continue; + + _seekToBlock(blockNr); + if (_file->write(block->block, 4096) != 4096) + { + std::string errmsg = "Error writing to device: "+_file->errorString().toStdString(); + throw std::runtime_error(errmsg); + } + block->dirty = false; + } + + if (_blockcache.contains(0)) + { + /* Write first block with MBR */ + auto block = _blockcache.value(0); + + if (block->dirty) + { + _seekToBlock(0); + if (_file->write(block->block, 4096) != 4096) + { + std::string errmsg = "Error writing MBR to device: "+_file->errorString().toStdString(); + throw std::runtime_error(errmsg); + } + block->dirty = false; + } + } + + _dirty = false; +} + +void DeviceWrapper::_readIntoBlockCacheIfNeeded(quint64 offset, quint64 size) +{ + if (!size) + return; + + quint64 firstBlock = offset/4096; + quint64 lastBlock = (offset+size)/4096; + + for (auto i = firstBlock; i <= lastBlock; i++) + { + if (!_blockcache.contains(i)) + { + _seekToBlock(i); + + auto cacheEntry = new DeviceWrapperBlockCacheEntry(this); + int bytesRead = _file->read(cacheEntry->block, 4096); + if (bytesRead != 4096) + { + std::string errmsg = "Error reading from device: "+_file->errorString().toStdString(); + throw std::runtime_error(errmsg); + } + _blockcache.insert(i, cacheEntry); + } + } +} + +void DeviceWrapper::pread(char *buf, quint64 size, quint64 offset) +{ + if (!size) + return; + + _readIntoBlockCacheIfNeeded(offset, size); + quint64 firstBlock = offset / 4096; + quint64 offsetInBlock = offset % 4096; + + for (auto i = firstBlock; size; i++) + { + auto block = _blockcache.value(i); + size_t bytesToCopyFromBlock = qMin(4096-offsetInBlock, size); + memcpy(buf, block->block + offsetInBlock, bytesToCopyFromBlock); + + buf += bytesToCopyFromBlock; + size -= bytesToCopyFromBlock; + offsetInBlock = 0; + } +} + +void DeviceWrapper::pwrite(const char *buf, quint64 size, quint64 offset) +{ + if (!size) + return; + + quint64 firstBlock = offset / 4096; + quint64 offsetInBlock = offset % 4096; + + if (offsetInBlock || size % 4096) + { + /* Need to read existing data from disk + as we will only be replacing a part of a block. */ + _readIntoBlockCacheIfNeeded(offset, size); + } + + for (auto i = firstBlock; size; i++) + { + auto block = _blockcache.value(i, NULL); + if (!block) + { + block = new DeviceWrapperBlockCacheEntry(this); + _blockcache.insert(i, block); + } + + block->dirty = true; + size_t bytesToCopyFromBlock = qMin(4096-offsetInBlock, size); + memcpy(block->block + offsetInBlock, buf, bytesToCopyFromBlock); + + buf += bytesToCopyFromBlock; + size -= bytesToCopyFromBlock; + offsetInBlock = 0; + } + + _dirty = true; +} + +DeviceWrapperFatPartition *DeviceWrapper::fatPartition(int nr) +{ + if (nr > 4 || nr < 1) + throw std::runtime_error("Only basic partitions 1-4 supported"); + + struct mbr_table mbr; + pread((char *) &mbr, sizeof(mbr), 0); + + if (mbr.signature[0] != 0x55 || mbr.signature[1] != 0xAA) + throw std::runtime_error("MBR does not have valid signature"); + + if (!mbr.part[nr-1].starting_sector || !mbr.part[nr-1].nr_of_sectors) + throw std::runtime_error("Partition does not exist"); + + return new DeviceWrapperFatPartition(this, mbr.part[nr-1].starting_sector*512, mbr.part[nr-1].nr_of_sectors*512, this); +} + diff --git a/src/devicewrapper.h b/src/devicewrapper.h new file mode 100644 index 0000000..c5b6d14 --- /dev/null +++ b/src/devicewrapper.h @@ -0,0 +1,50 @@ +#ifndef DEVICEWRAPPER_H +#define DEVICEWRAPPER_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include +#include +#include + +class DeviceWrapperBlockCacheEntry; +class DeviceWrapperFatPartition; + +#ifdef Q_OS_WIN +#include "windows/winfile.h" +typedef WinFile DeviceWrapperFile; +#elif defined(Q_OS_DARWIN) +#include "mac/macfile.h" +typedef MacFile DeviceWrapperFile; +#else +typedef QFile DeviceWrapperFile; +#endif + + +class DeviceWrapper : public QObject +{ + Q_OBJECT +public: + explicit DeviceWrapper(DeviceWrapperFile *file, QObject *parent = nullptr); + virtual ~DeviceWrapper(); + void sync(); + void pwrite(const char *buf, quint64 size, quint64 offset); + void pread(char *buf, quint64 size, quint64 offset); + DeviceWrapperFatPartition *fatPartition(int nr); + +protected: + bool _dirty; + QMap _blockcache; + DeviceWrapperFile *_file; + + void _readIntoBlockCacheIfNeeded(quint64 offset, quint64 size); + void _seekToBlock(quint64 blockNr); + +signals: + +}; + +#endif // DEVICEWRAPPER_H diff --git a/src/devicewrapperblockcacheentry.cpp b/src/devicewrapperblockcacheentry.cpp new file mode 100644 index 0000000..2879dc5 --- /dev/null +++ b/src/devicewrapperblockcacheentry.cpp @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include "devicewrapperblockcacheentry.h" +#include + +DeviceWrapperBlockCacheEntry::DeviceWrapperBlockCacheEntry(QObject *parent, size_t blocksize) + : QObject(parent), dirty(false) +{ + /* Windows requires buffers to be 4k aligned when reading/writing raw disk devices */ + block = (char *) qMallocAligned(blocksize, 4096); +} + +DeviceWrapperBlockCacheEntry::~DeviceWrapperBlockCacheEntry() +{ + qFreeAligned(block); +} diff --git a/src/devicewrapperblockcacheentry.h b/src/devicewrapperblockcacheentry.h new file mode 100644 index 0000000..402cc2e --- /dev/null +++ b/src/devicewrapperblockcacheentry.h @@ -0,0 +1,21 @@ +#ifndef DEVICEWRAPPERBLOCKCACHEENTRY_H +#define DEVICEWRAPPERBLOCKCACHEENTRY_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include + +class DeviceWrapperBlockCacheEntry : QObject +{ + Q_OBJECT +public: + DeviceWrapperBlockCacheEntry(QObject *parent, size_t blocksize = 4096); + ~DeviceWrapperBlockCacheEntry(); + char *block; + bool dirty; +}; + +#endif // DEVICEWRAPPERBLOCKCACHEENTRY_H diff --git a/src/devicewrapperfatpartition.cpp b/src/devicewrapperfatpartition.cpp new file mode 100644 index 0000000..28340df --- /dev/null +++ b/src/devicewrapperfatpartition.cpp @@ -0,0 +1,634 @@ +#include "devicewrapperfatpartition.h" +#include "devicewrapperstructs.h" +#include "qdebug.h" + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +DeviceWrapperFatPartition::DeviceWrapperFatPartition(DeviceWrapper *dw, quint64 partStart, quint64 partLen, QObject *parent) + : DeviceWrapperPartition(dw, partStart, partLen, parent) +{ + union fat_bpb bpb; + + read((char *) &bpb, sizeof(bpb)); + + if (bpb.fat16.Signature[0] != 0x55 || bpb.fat16.Signature[1] != 0xAA) + throw std::runtime_error("Partition does not have a FAT file system"); + + /* Determine FAT type as per p. 14 https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf */ + _bytesPerSector = bpb.fat16.BPB_BytsPerSec; + uint32_t totalSectors, dataSectors, countOfClusters; + _fat16_rootDirSectors = ((bpb.fat16.BPB_RootEntCnt * 32) + (_bytesPerSector - 1)) / _bytesPerSector; + + if (bpb.fat16.BPB_FATSz16) + _fatSize = bpb.fat16.BPB_FATSz16; + else + _fatSize = bpb.fat32.BPB_FATSz32; + + if (bpb.fat16.BPB_TotSec16) + totalSectors = bpb.fat16.BPB_TotSec16; + else + totalSectors = bpb.fat32.BPB_TotSec32; + + dataSectors = totalSectors - (bpb.fat16.BPB_RsvdSecCnt + (bpb.fat16.BPB_NumFATs * _fatSize) + _fat16_rootDirSectors); + countOfClusters = dataSectors / bpb.fat16.BPB_SecPerClus; + _bytesPerCluster = bpb.fat16.BPB_SecPerClus * _bytesPerSector; + _fat16_firstRootDirSector = bpb.fat16.BPB_RsvdSecCnt + (bpb.fat16.BPB_NumFATs * bpb.fat16.BPB_FATSz16); + _fat32_firstRootDirCluster = bpb.fat32.BPB_RootClus; + + if (!_bytesPerSector) + _type = EXFAT; + else if (countOfClusters < 4085) + _type = FAT12; + else if (countOfClusters < 65525) + _type = FAT16; + else + _type = FAT32; + + if (_type == FAT12) + throw std::runtime_error("FAT12 file system not supported"); + if (_type == EXFAT) + throw std::runtime_error("exFAT file system not supported"); + if (_bytesPerSector % 4) + throw std::runtime_error("FAT file system: invalid bytes per sector"); + + _firstFatStartOffset = bpb.fat16.BPB_RsvdSecCnt * _bytesPerSector; + for (int i = 0; i < bpb.fat16.BPB_NumFATs; i++) + { + _fatStartOffset.append(_firstFatStartOffset + (i * _fatSize * _bytesPerSector)); + } + + if (_type == FAT16) + { + _fat32_fsinfoSector = 0; + _clusterOffset = (_fat16_firstRootDirSector+_fat16_rootDirSectors) * _bytesPerSector; + } + else + { + _fat32_fsinfoSector = bpb.fat32.BPB_FSInfo; + _clusterOffset = _firstFatStartOffset + (bpb.fat16.BPB_NumFATs * _fatSize * _bytesPerSector); + } +} + +uint32_t DeviceWrapperFatPartition::allocateCluster() +{ + char sector[_bytesPerSector]; + int bytesPerEntry = (_type == FAT16 ? 2 : 4); + int entriesPerSector = _bytesPerSector/bytesPerEntry; + uint32_t cluster; + uint16_t *f16 = (uint16_t *) §or; + uint32_t *f32 = (uint32_t *) §or; + + seek(_firstFatStartOffset); + + for (int i = 0; i < _fatSize; i++) + { + read(sector, sizeof(sector)); + + for (int j=0; j < entriesPerSector; j++) + { + if (_type == FAT16) + { + if (f16[j] == 0) + { + /* Found available FAT16 cluster, mark it used/EOF */ + cluster = j+i*entriesPerSector; + setFAT16(cluster, 0xFFFF); + return cluster; + } + } + else + { + if ( (f32[j] & 0x0FFFFFFF) == 0) + { + /* Found available FAT32 cluster, mark it used/EOF */ + cluster = j+i*entriesPerSector; + setFAT32(cluster, 0xFFFFFFF); + updateFSinfo(-1, cluster); + return cluster; + } + } + } + } + + throw std::runtime_error("Out of disk space on FAT partition"); +} + +uint32_t DeviceWrapperFatPartition::allocateCluster(uint32_t previousCluster) +{ + uint32_t newCluster = allocateCluster(); + + if (previousCluster) + { + if (_type == FAT16) + setFAT16(previousCluster, newCluster); + else + setFAT32(previousCluster, newCluster); + } + + return newCluster; +} + +void DeviceWrapperFatPartition::setFAT16(uint16_t cluster, uint16_t value) +{ + /* Modify all FATs (usually 2) */ + for (auto fatStart : qAsConst(_fatStartOffset)) + { + seek(fatStart + cluster * 2); + write((char *) &value, 2); + } +} + +void DeviceWrapperFatPartition::setFAT32(uint32_t cluster, uint32_t value) +{ + uint32_t prev_value, reserved_bits; + + /* Modify all FATs (usually 2) */ + for (auto fatStart : qAsConst(_fatStartOffset)) + { + /* Spec (p. 16) mentions we must preserve high 4 bits of FAT32 FAT entry when modifiying */ + seek(fatStart + cluster * 4); + read( (char *) &prev_value, 4); + reserved_bits = prev_value & 0xF0000000; + value |= reserved_bits; + + seek(fatStart + cluster * 4); + write((char *) &value, 4); + } +} + +void DeviceWrapperFatPartition::setFAT(uint32_t cluster, uint32_t value) +{ + if (_type == FAT16) + setFAT16(cluster, value); + else + setFAT32(cluster, value); +} + +uint32_t DeviceWrapperFatPartition::getFAT(uint32_t cluster) +{ + if (_type == FAT16) + { + uint16_t result; + seek(_firstFatStartOffset + cluster * 2); + read((char *) &result, 2); + return result; + } + else + { + uint32_t result; + seek(_firstFatStartOffset + cluster * 4); + read((char *) &result, 4); + return result & 0x0FFFFFFF; + } +} + +QList DeviceWrapperFatPartition::getClusterChain(uint32_t firstCluster) +{ + QList list; + uint32_t cluster = firstCluster; + + while (true) + { + if ( (_type == FAT16 && cluster > 0xFFF7) + || (_type == FAT32 && cluster > 0xFFFFFF7)) + { + /* Reached EOF */ + break; + } + + if (list.contains(cluster)) + throw std::runtime_error("Corrupt file system. Circular references in FAT table"); + + list.append(cluster); + cluster = getFAT(cluster); + } + + return list; +} + +void DeviceWrapperFatPartition::seekCluster(uint32_t cluster) +{ + seek(_clusterOffset + (cluster-2)*_bytesPerCluster); +} + +bool DeviceWrapperFatPartition::fileExists(const QString &filename) +{ + struct dir_entry entry; + return getDirEntry(filename, &entry); +} + +QByteArray DeviceWrapperFatPartition::readFile(const QString &filename) +{ + struct dir_entry entry; + + if (!getDirEntry(filename, &entry)) + return QByteArray(); /* File not found */ + + uint32_t firstCluster = entry.DIR_FstClusLO; + if (_type == FAT32) + firstCluster |= (entry.DIR_FstClusHI << 16); + QList clusterList = getClusterChain(firstCluster); + uint32_t len = entry.DIR_FileSize, pos = 0; + QByteArray result(len, 0); + + for (uint32_t cluster : qAsConst(clusterList)) + { + seekCluster(cluster); + read(result.data()+pos, qMin(_bytesPerCluster, len-pos)); + + pos += _bytesPerCluster; + if (pos >= len) + break; + } + + return result; +} + +void DeviceWrapperFatPartition::writeFile(const QString &filename, const QByteArray &contents) +{ + QList clusterList; + uint32_t pos = 0; + uint32_t firstCluster; + int clustersNeeded = (contents.length() + _bytesPerCluster - 1) / _bytesPerCluster; + struct dir_entry entry; + + getDirEntry(filename, &entry, true); + firstCluster = entry.DIR_FstClusLO; + if (_type == FAT32) + firstCluster |= (entry.DIR_FstClusHI << 16); + + if (firstCluster) + clusterList = getClusterChain(firstCluster); + + if (clusterList.length() < clustersNeeded) + { + /* We need to allocate more clusters */ + uint32_t lastCluster = 0; + int extraClustersNeeded = clustersNeeded - clusterList.length(); + + if (!clusterList.isEmpty()) + lastCluster = clusterList.last(); + + for (int i = 0; i < extraClustersNeeded; i++) + { + lastCluster = allocateCluster(lastCluster); + clusterList.append(lastCluster); + } + } + else if (clusterList.length() > clustersNeeded) + { + /* We need to remove excess clusters */ + int clustersToRemove = clusterList.length() - clustersNeeded; + uint32_t clusterToRemove = 0; + QByteArray zeroes(_bytesPerCluster, 0); + + for (int i=0; i < clustersToRemove; i++) + { + clusterToRemove = clusterList.takeLast(); + + /* Zero out previous data in excess clusters, + just in case someone wants to take a disk image later */ + seekCluster(clusterToRemove); + write(zeroes.data(), zeroes.length()); + + /* Mark cluster available again in FAT */ + setFAT(clusterToRemove, 0); + } + updateFSinfo(clustersToRemove, clusterToRemove); + + if (!clusterList.isEmpty()) + { + if (_type == FAT16) + setFAT16(clusterList.last(), 0xFFFF); + else + setFAT32(clusterList.last(), 0xFFFFFFF); + } + } + + //qDebug() << "First cluster:" << firstCluster << "Clusters:" << clusterList; + + /* Write file data */ + for (uint32_t cluster : qAsConst(clusterList)) + { + seekCluster(cluster); + write(contents.data()+pos, qMin(_bytesPerCluster, contents.length()-pos)); + + pos += _bytesPerCluster; + if (pos >= contents.length()) + break; + } + + if (clustersNeeded) + { + /* Zero out last cluster tip */ + uint32_t extraBytesAtEndOfCluster = _bytesPerCluster - (contents.length() % _bytesPerCluster); + if (extraBytesAtEndOfCluster) + { + QByteArray zeroes(extraBytesAtEndOfCluster, 0); + write(zeroes.data(), zeroes.length()); + } + } + + /* Update directory entry */ + if (clusterList.isEmpty()) + firstCluster = (_type == FAT16 ? 0xFFFF : 0xFFFFFFF); + else + firstCluster = clusterList.first(); + + entry.DIR_FstClusLO = (firstCluster & 0xFFFF); + entry.DIR_FstClusHI = (firstCluster >> 16); + entry.DIR_WrtDate = QDateToFATdate( QDate::currentDate() ); + entry.DIR_WrtTime = QTimeToFATtime( QTime::currentTime() ); + entry.DIR_LstAccDate = entry.DIR_WrtDate; + entry.DIR_FileSize = contents.length(); + updateDirEntry(&entry); +} + +bool DeviceWrapperFatPartition::getDirEntry(const QString &longFilename, struct dir_entry *entry, bool createIfNotExist) +{ + QString filenameRead, longFilenameLower = longFilename.toLower(); + + if (longFilename.isEmpty()) + throw std::runtime_error("Filename cannot not be empty"); + + openDir(); + while (readDir(entry)) + { + if (entry->DIR_Attr & ATTR_LONG_NAME) + { + struct longfn_entry *l = (struct longfn_entry *) entry; + /* A part can have 13 UTF-16 characters */ + QString lnamePart(13, QChar::Null); + char *lnamePartStr = (char *) lnamePart.data(); + /* Using memcpy() because it has no problems accessing unaligned struct members */ + memcpy(lnamePartStr, l->LDIR_Name1, 10); + memcpy(lnamePartStr+10, l->LDIR_Name2, 12); + memcpy(lnamePartStr+22, l->LDIR_Name3, 4); + filenameRead = lnamePart + filenameRead; + } + else + { + if (entry->DIR_Name[0] != 0xE5) + { + filenameRead.truncate(filenameRead.indexOf(QChar::Null)); + + //qDebug() << "Long filename:" << filenameRead << "Short:" << QByteArray(entry->DIR_Name, sizeof(entry->DIR_Name)); + + /* FIXME: should we check short file names as well, if they are not preceeded by long file name entry? */ + + if (filenameRead.toLower() == longFilenameLower) + { + return true; + } + } + + filenameRead.clear(); + } + } + + if (createIfNotExist) + { + QByteArray shortFilename; + uint8_t shortFileNameChecksum = 0; + struct longfn_entry longEntry; + + if (longFilename.count(".") == 1) + { + QList fnParts = longFilename.toLatin1().toUpper().split('.'); + shortFilename = fnParts[0].leftJustified(8, ' ', true)+fnParts[1].leftJustified(3, ' ', true); + } + else + { + shortFilename = longFilename.toLatin1().leftJustified(11, ' ', true); + } + + /* Verify short file name has not been taken yet, and if not try inserting numbers into the name */ + if (dirNameExists(shortFilename)) + { + for (int i=0; i<100; i++) + { + shortFilename = shortFilename.left( (i < 10 ? 7 : 6) )+QByteArray::number(i)+shortFilename.right(3); + + if (!dirNameExists(shortFilename)) + { + break; + } + else if (i == 99) + { + throw std::runtime_error("Error finding available short filename"); + } + } + } + + for(int i = 0; i < shortFilename.length(); i++) + { + shortFileNameChecksum = ((shortFileNameChecksum & 1) ? 0x80 : 0) + (shortFileNameChecksum >> 1) + shortFilename[i]; + } + + QString longFilenameWithNull = longFilename + QChar::Null; + char *longFilenameStr = (char *) longFilenameWithNull.data(); + int lfnFragments = (longFilenameWithNull.length()+12)/13; + int lenBytes = longFilenameWithNull.length()*2; + + /* long file name directory entries are added in reverse order before the 8.3 entry */ + for (int i = lfnFragments; i > 0; i--) + { + memset(&longEntry, 0xff, sizeof(longEntry)); + longEntry.LDIR_Attr = ATTR_LONG_NAME; + longEntry.LDIR_Chksum = shortFileNameChecksum; + longEntry.LDIR_Ord = (i == lfnFragments) ? lfnFragments | LAST_LONG_ENTRY : lfnFragments; + longEntry.LDIR_FstClusLO = 0; + longEntry.LDIR_Type = 0; + + size_t start = (i-1) * 26; + memcpy(longEntry.LDIR_Name1, longFilenameStr+start, qMin(lenBytes-start, sizeof(longEntry.LDIR_Name1))); + start += sizeof(longEntry.LDIR_Name1); + if (start < lenBytes) + { + memcpy(longEntry.LDIR_Name2, longFilenameStr+start, qMin(lenBytes-start, sizeof(longEntry.LDIR_Name2))); + start += sizeof(longEntry.LDIR_Name2); + if (start < lenBytes) + { + memcpy(longEntry.LDIR_Name3, longFilenameStr+start, qMin(lenBytes-start, sizeof(longEntry.LDIR_Name3))); + } + } + + writeDirEntryAtCurrentPos((struct dir_entry *) &longEntry); + } + + memset(entry, 0, sizeof(*entry)); + memcpy(entry->DIR_Name, shortFilename.data(), sizeof(entry->DIR_Name)); + entry->DIR_Attr = ATTR_ARCHIVE; + entry->DIR_CrtDate = QDateToFATdate( QDate::currentDate() ); + entry->DIR_CrtTime = QTimeToFATtime( QTime::currentTime() ); + + writeDirEntryAtCurrentPos(entry); + + /* Add an end-of-directory marker after our newly appended file */ + struct dir_entry endOfDir = {0}; + writeDirEntryAtCurrentPos(&endOfDir); + } + + return false; +} + +bool DeviceWrapperFatPartition::dirNameExists(const QByteArray dirname) +{ + struct dir_entry entry; + + openDir(); + while (readDir(&entry)) + { + if (!(entry.DIR_Attr & ATTR_LONG_NAME) + && dirname == QByteArray(entry.DIR_Name, sizeof(entry.DIR_Name))) + { + return true; + } + } + + return false; +} + +void DeviceWrapperFatPartition::updateDirEntry(struct dir_entry *dirEntry) +{ + struct dir_entry iterEntry; + + openDir(); + while (readDir(&iterEntry)) + { + /* Look for existing entry with same short filename */ + if (!(iterEntry.DIR_Attr & ATTR_LONG_NAME) + && memcmp(dirEntry->DIR_Name, iterEntry.DIR_Name, sizeof(iterEntry.DIR_Name)) == 0) + { + /* seek() back and write out new entry */ + _offset -= sizeof(*dirEntry); + write((char *) dirEntry, sizeof(*dirEntry)); + return; + } + } + + throw std::runtime_error("Error locating existing directory entry"); +} + +void DeviceWrapperFatPartition::writeDirEntryAtCurrentPos(struct dir_entry *dirEntry) +{ + write((char *) dirEntry, sizeof(*dirEntry)); + + if (_type == FAT32) + { + if ((pos()-_clusterOffset) % _bytesPerCluster == 0) + { + /* We reached the end of the cluster, allocate/seek to next cluster */ + uint32_t nextCluster = getFAT(_fat32_currentRootDirCluster); + /* FIXME: should we check for circular cluster references? */ + + if (nextCluster > 0xFFFFFF7) + { + nextCluster = allocateCluster(_fat32_currentRootDirCluster); + } + + _fat32_currentRootDirCluster = nextCluster; + seekCluster(_fat32_currentRootDirCluster); + } + } + else if (pos() > (_fat16_firstRootDirSector+_fat16_rootDirSectors)*_bytesPerSector) + { + throw std::runtime_error("FAT16: ran out of root directory entry space"); + } +} + +void DeviceWrapperFatPartition::openDir() +{ + /* Seek to start of root directory */ + if (_type == FAT16) + { + seek(_fat16_firstRootDirSector * _bytesPerSector); + } + else + { + _fat32_currentRootDirCluster = _fat32_firstRootDirCluster; + seekCluster(_fat32_currentRootDirCluster); + } +} + +bool DeviceWrapperFatPartition::readDir(struct dir_entry *result) +{ + quint64 oldOffset = _offset; + read((char *) result, sizeof(*result)); + + if (result->DIR_Name[0] == 0) + { + /* seek() back to start of the entry marking end of directory */ + _offset = oldOffset; + return false; + } + + if (_type == FAT32) + { + if ((pos()-_clusterOffset) % _bytesPerCluster == 0) + { + /* We reached the end of the cluster, seek to next cluster */ + uint32_t nextCluster = getFAT(_fat32_currentRootDirCluster); + /* FIXME: should we check for circular cluster references? */ + + if (nextCluster > 0xFFFFFF7) + throw std::runtime_error("Reached end of FAT32 root directory, but no end-of-directory marker found"); + + _fat32_currentRootDirCluster = nextCluster; + seekCluster(_fat32_currentRootDirCluster); + } + } + else if (pos() > (_fat16_firstRootDirSector+_fat16_rootDirSectors)*_bytesPerSector) + { + throw std::runtime_error("Reached end of FAT16 root directory section, but no end-of-directory marker found"); + } + + return true; +} + +void DeviceWrapperFatPartition::updateFSinfo(int deltaClusters, uint32_t nextFreeClusterHint) +{ + struct FSInfo fsinfo; + + if (!_fat32_fsinfoSector) + return; + + seek(_fat32_fsinfoSector * _bytesPerSector); + read((char *) &fsinfo, sizeof(fsinfo)); + + if (fsinfo.FSI_LeadSig[0] != 0x52 || fsinfo.FSI_LeadSig[1] != 0x52 + || fsinfo.FSI_LeadSig[2] != 0x61 || fsinfo.FSI_LeadSig[3] != 0x41 + || fsinfo.FSI_StrucSig[0] != 0x72 || fsinfo.FSI_StrucSig[1] != 0x72 + || fsinfo.FSI_StrucSig[2] != 0x41 || fsinfo.FSI_StrucSig[3] != 0x61 + || fsinfo.FSI_TrailSig[0] != 0x00 || fsinfo.FSI_TrailSig[1] != 0x00 + || fsinfo.FSI_TrailSig[2] != 0x55 || fsinfo.FSI_TrailSig[3] != 0xAA) + { + throw std::runtime_error("FAT32 FSinfo structure corrupt. Signature does not match."); + } + + if (deltaClusters != 0 && fsinfo.FSI_Free_Count != 0xFFFFFFFF) + { + fsinfo.FSI_Free_Count += deltaClusters; + } + + if (nextFreeClusterHint) + { + fsinfo.FSI_Nxt_Free = nextFreeClusterHint; + } + + seek(_fat32_fsinfoSector * _bytesPerSector); + write((char *) &fsinfo, sizeof(fsinfo)); +} + +uint16_t DeviceWrapperFatPartition::QTimeToFATtime(const QTime &time) +{ + return (time.hour() << 11) | (time.minute() << 5) | (time.second() >> 1) ; +} + +uint16_t DeviceWrapperFatPartition::QDateToFATdate(const QDate &date) +{ + return ((date.year() - 1980) << 9) | (date.month() << 5) | date.day(); +} diff --git a/src/devicewrapperfatpartition.h b/src/devicewrapperfatpartition.h new file mode 100644 index 0000000..ed22afc --- /dev/null +++ b/src/devicewrapperfatpartition.h @@ -0,0 +1,54 @@ +#ifndef DEVICEWRAPPERFATPARTITION_H +#define DEVICEWRAPPERFATPARTITION_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include "devicewrapperpartition.h" +#include +#include +#include + +enum fatType { FAT12, FAT16, FAT32, EXFAT }; +struct dir_entry; + +class DeviceWrapperFatPartition : public DeviceWrapperPartition +{ + Q_OBJECT +public: + DeviceWrapperFatPartition(DeviceWrapper *dw, quint64 partStart, quint64 partLen, QObject *parent = nullptr); + + QByteArray readFile(const QString &filename); + void writeFile(const QString &filename, const QByteArray &contents); + bool fileExists(const QString &filename); + +protected: + enum fatType _type; + uint32_t _firstFatStartOffset, _fatSize, _bytesPerCluster, _clusterOffset; + uint32_t _fat16_rootDirSectors, _fat16_firstRootDirSector; + uint32_t _fat32_firstRootDirCluster, _fat32_currentRootDirCluster; + uint16_t _bytesPerSector, _fat32_fsinfoSector; + QList _fatStartOffset; + + QList getClusterChain(uint32_t firstCluster); + void setFAT16(uint16_t cluster, uint16_t value); + void setFAT32(uint32_t cluster, uint32_t value); + void setFAT(uint32_t cluster, uint32_t value); + uint32_t getFAT(uint32_t cluster); + void seekCluster(uint32_t cluster); + uint32_t allocateCluster(); + uint32_t allocateCluster(uint32_t previousCluster); + bool getDirEntry(const QString &longFilename, struct dir_entry *entry, bool createIfNotExist = false); + bool dirNameExists(const QByteArray dirname); + void updateDirEntry(struct dir_entry *dirEntry); + void writeDirEntryAtCurrentPos(struct dir_entry *dirEntry); + void openDir(); + bool readDir(struct dir_entry *result); + void updateFSinfo(int deltaClusters, uint32_t nextFreeClusterHint); + uint16_t QTimeToFATtime(const QTime &time); + uint16_t QDateToFATdate(const QDate &date); +}; + +#endif // DEVICEWRAPPERFATPARTITION_H diff --git a/src/devicewrapperpartition.cpp b/src/devicewrapperpartition.cpp new file mode 100644 index 0000000..fc292e6 --- /dev/null +++ b/src/devicewrapperpartition.cpp @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include "devicewrapperpartition.h" +#include "devicewrapper.h" + +DeviceWrapperPartition::DeviceWrapperPartition(DeviceWrapper *dw, quint64 partStart, quint64 partLen, QObject *parent) + : QObject{parent}, _dw(dw), _partStart(partStart), _partLen(partLen), _offset(partStart) +{ + _partEnd = _partStart + _partLen; +} + +DeviceWrapperPartition::~DeviceWrapperPartition() +{ + +} + +void DeviceWrapperPartition::read(char *data, qint64 size) +{ + if (_offset+size > _partEnd) + { + throw std::runtime_error("Error: trying to read beyond partition"); + } + + _dw->pread(data, size, _offset); + _offset += size; +} + +void DeviceWrapperPartition::seek(qint64 pos) +{ + if (pos > _partLen) + { + throw std::runtime_error("Error: trying to seek beyond partition"); + } + _offset = pos+_partStart; +} + +qint64 DeviceWrapperPartition::pos() const +{ + return _offset-_partStart; +} + +void DeviceWrapperPartition::write(const char *data, qint64 size) +{ + if (_offset+size > _partEnd) + { + throw std::runtime_error("Error: trying to write beyond partition"); + } + + _dw->pwrite(data, size, _offset); + _offset += size; +} diff --git a/src/devicewrapperpartition.h b/src/devicewrapperpartition.h new file mode 100644 index 0000000..ab06402 --- /dev/null +++ b/src/devicewrapperpartition.h @@ -0,0 +1,32 @@ +#ifndef DEVICEWRAPPERPARTITION_H +#define DEVICEWRAPPERPARTITION_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +#include + +class DeviceWrapper; + +class DeviceWrapperPartition : public QObject +{ + Q_OBJECT +public: + explicit DeviceWrapperPartition(DeviceWrapper *dw, quint64 partStart, quint64 partLen, QObject *parent = nullptr); + virtual ~DeviceWrapperPartition(); + void read(char *data, qint64 size); + void seek(qint64 pos); + qint64 pos() const; + void write(const char *data, qint64 size); + +protected: + DeviceWrapper *_dw; + quint64 _partStart, _partLen, _partEnd, _offset; + +signals: + +}; + +#endif // DEVICEWRAPPERPARTITION_H diff --git a/src/devicewrapperstructs.h b/src/devicewrapperstructs.h new file mode 100644 index 0000000..fbbf0c8 --- /dev/null +++ b/src/devicewrapperstructs.h @@ -0,0 +1,145 @@ +#ifndef DEVICEWRAPPERSTRUCTS_H +#define DEVICEWRAPPERSTRUCTS_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2022 Raspberry Pi Ltd + */ + +/* MBR on-disk structures */ + +struct mbr_partition_entry { + unsigned char bootable; + char begin_hsc[3]; + unsigned char id; + char end_hsc[3]; + unsigned int starting_sector; + unsigned int nr_of_sectors; +} __attribute__ ((packed)); + +struct mbr_table { + char bootcode[440]; + unsigned char diskid[4]; + unsigned char flags[2]; + mbr_partition_entry part[4]; + unsigned char signature[2]; +} __attribute__ ((packed)); + + +/* File Allocation Table + * https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf + */ + +struct fat16_bpb { + uint8_t BS_jmpBoot[3]; + char BS_OEMName[8]; + uint16_t BPB_BytsPerSec; + uint8_t BPB_SecPerClus; + uint16_t BPB_RsvdSecCnt; + uint8_t BPB_NumFATs; + uint16_t BPB_RootEntCnt; + uint16_t BPB_TotSec16; + uint8_t BPB_Media; + uint16_t BPB_FATSz16; + uint16_t BPB_SecPerTrk; + uint16_t BPB_NumHeads; + uint32_t BPB_HiddSec; + uint32_t BPB_TotSec32; + + uint8_t BS_DrvNum; + uint8_t BS_Reserved1; + uint8_t BS_BootSig; + uint32_t BS_VolID; + char BS_VolLab[11]; + char BS_FilSysType[8]; + + uint8_t Zeroes[448]; + uint8_t Signature[2]; /* 0x55aa */ +} __attribute__ ((packed)); + +struct fat32_bpb { + uint8_t BS_jmpBoot[3]; + char BS_OEMName[8]; + uint16_t BPB_BytsPerSec; + uint8_t BPB_SecPerClus; + uint16_t BPB_RsvdSecCnt; + uint8_t BPB_NumFATs; + uint16_t BPB_RootEntCnt; + uint16_t BPB_TotSec16; + uint8_t BPB_Media; + uint16_t BPB_FATSz16; + uint16_t BPB_SecPerTrk; + uint16_t BPB_NumHeads; + uint32_t BPB_HiddSec; + uint32_t BPB_TotSec32; + + uint32_t BPB_FATSz32; + uint16_t BPB_ExtFlags; + uint16_t BPB_FSVer; + uint32_t BPB_RootClus; + uint16_t BPB_FSInfo; + uint16_t BPB_BkBootSec; + uint8_t BPB_Reserved[12]; + uint8_t BS_DrvNum; + uint8_t BS_Reserved1; + uint8_t BS_BootSig; + uint32_t BS_VolID; + char BS_VolLab[11]; + char BS_FilSysType[8]; + + uint8_t Zeroes[420]; + uint8_t Signature[2]; /* 0x55aa */ +} __attribute__ ((packed)); + +union fat_bpb { + struct fat16_bpb fat16; + struct fat32_bpb fat32; +}; + +struct dir_entry { + char DIR_Name[11]; + uint8_t DIR_Attr; + uint8_t DIR_NTRes; + uint8_t DIR_CrtTimeTenth; + uint16_t DIR_CrtTime; + uint16_t DIR_CrtDate; + uint16_t DIR_LstAccDate; + uint16_t DIR_FstClusHI; + uint16_t DIR_WrtTime; + uint16_t DIR_WrtDate; + uint16_t DIR_FstClusLO; + uint32_t DIR_FileSize; +} __attribute__ ((packed)); + +struct longfn_entry { + uint8_t LDIR_Ord; + char LDIR_Name1[10]; + uint8_t LDIR_Attr; + uint8_t LDIR_Type; + uint8_t LDIR_Chksum; + char LDIR_Name2[12]; + uint16_t LDIR_FstClusLO; + char LDIR_Name3[4]; +} __attribute__ ((packed)); + +#define LAST_LONG_ENTRY 0x40 + +#define ATTR_READ_ONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLUME_ID 0x08 +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID) + +struct FSInfo { + uint8_t FSI_LeadSig[4]; /* 0x52 0x52 0x61 0x41 */ + uint8_t FSI_Reserved1[480]; + uint8_t FSI_StrucSig[4]; /* 0x72 0x72 0x41 0x61 */ + uint32_t FSI_Free_Count; + uint32_t FSI_Nxt_Free; + uint8_t FSI_Reserved2[12]; + uint8_t FSI_TrailSig[4]; /* 0x00 0x00 0x55 0xAA */ +} __attribute__ ((packed)); + +#endif // DEVICEWRAPPERSTRUCTS_H