Add classes for mounting FAT16/32 without help from OS

Minimal implementation for reading/writing files in the root
directory of a FAT16/FAT32 file system.

Can read/write from raw disk devices, and no longer relies on
operating system support for mounting the file system.

Currently assumes Imager will always be run on 'little endian'
architectures such as Intel and ARM (at least under Linux).
If there is a use-case for big-endian (anybody still using Sparc?)
this may be revisited later.
This commit is contained in:
Floris Bos 2022-11-14 20:18:36 +01:00
parent 30225187bd
commit 142ddfc037
10 changed files with 1193 additions and 1 deletions

175
src/devicewrapper.cpp Normal file
View file

@ -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 <QDebug>
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);
}