Switch to using FAT classes for advanced settings

- No longer relies on operating system for mounting FAT partition
  when applying 'advanced settings'
- change '__attribute__ ((packed))' to '#pragma pack()' as the
  mingw version we are using for Windows has a bug with the former
This commit is contained in:
Floris Bos 2022-11-15 01:15:08 +01:00
parent ebc6edc0c3
commit 5fa3fbe8dc
5 changed files with 142 additions and 321 deletions

12
debian/changelog vendored
View file

@ -1,3 +1,15 @@
rpi-imager (1.7.4) unstable; urgency=medium
* Advanced settings: fix escaping single quotes
* Advanced settings: default to using username of logged-in user
* Now uses a different method to edit files on the FAT partition
to apply advanced settings. Imager now understands the FAT16/FAT32
file system format and can edit files by itself using the raw
disk device, without having to rely on the operating system
to mount the partition first.
-- Floris Bos <bos@je-eigen-domein.nl> Mon, 14 Nov 2022 21:49:27 +0100
rpi-imager (1.7.3) unstable; urgency=medium rpi-imager (1.7.3) unstable; urgency=medium
* Linux: use GnuTLS instead of OpenSSL for computing SHA256 * Linux: use GnuTLS instead of OpenSSL for computing SHA256

View file

@ -12,8 +12,8 @@ OPTION (ENABLE_TELEMETRY "Enable sending telemetry" ON)
project(rpi-imager LANGUAGES CXX C) project(rpi-imager LANGUAGES CXX C)
set(IMAGER_VERSION_MAJOR 1) set(IMAGER_VERSION_MAJOR 1)
set(IMAGER_VERSION_MINOR 7) set(IMAGER_VERSION_MINOR 7)
set(IMAGER_VERSION_STR "${IMAGER_VERSION_MAJOR}.${IMAGER_VERSION_MINOR}.3") set(IMAGER_VERSION_STR "${IMAGER_VERSION_MAJOR}.${IMAGER_VERSION_MINOR}.4")
set(IMAGER_VERSION_CSV "${IMAGER_VERSION_MAJOR},${IMAGER_VERSION_MINOR},3,0") set(IMAGER_VERSION_CSV "${IMAGER_VERSION_MAJOR},${IMAGER_VERSION_MINOR},4,0")
add_definitions(-DIMAGER_VERSION_STR="${IMAGER_VERSION_STR}") add_definitions(-DIMAGER_VERSION_STR="${IMAGER_VERSION_STR}")
add_definitions(-DIMAGER_VERSION_CSV=${IMAGER_VERSION_CSV}) add_definitions(-DIMAGER_VERSION_CSV=${IMAGER_VERSION_CSV})

View file

@ -483,7 +483,7 @@ bool DeviceWrapperFatPartition::dirNameExists(const QByteArray dirname)
while (readDir(&entry)) while (readDir(&entry))
{ {
if (!(entry.DIR_Attr & ATTR_LONG_NAME) if (!(entry.DIR_Attr & ATTR_LONG_NAME)
&& dirname == QByteArray(entry.DIR_Name, sizeof(entry.DIR_Name))) && dirname == QByteArray((char *) entry.DIR_Name, sizeof(entry.DIR_Name)))
{ {
return true; return true;
} }

View file

@ -6,6 +6,10 @@
* Copyright (C) 2022 Raspberry Pi Ltd * Copyright (C) 2022 Raspberry Pi Ltd
*/ */
#include <stdint.h>
#pragma pack(push, 1)
/* MBR on-disk structures */ /* MBR on-disk structures */
struct mbr_partition_entry { struct mbr_partition_entry {
@ -15,7 +19,7 @@ struct mbr_partition_entry {
char end_hsc[3]; char end_hsc[3];
unsigned int starting_sector; unsigned int starting_sector;
unsigned int nr_of_sectors; unsigned int nr_of_sectors;
} __attribute__ ((packed)); };
struct mbr_table { struct mbr_table {
char bootcode[440]; char bootcode[440];
@ -23,7 +27,7 @@ struct mbr_table {
unsigned char flags[2]; unsigned char flags[2];
mbr_partition_entry part[4]; mbr_partition_entry part[4];
unsigned char signature[2]; unsigned char signature[2];
} __attribute__ ((packed)); };
/* File Allocation Table /* File Allocation Table
@ -55,7 +59,7 @@ struct fat16_bpb {
uint8_t Zeroes[448]; uint8_t Zeroes[448];
uint8_t Signature[2]; /* 0x55aa */ uint8_t Signature[2]; /* 0x55aa */
} __attribute__ ((packed)); };
struct fat32_bpb { struct fat32_bpb {
uint8_t BS_jmpBoot[3]; uint8_t BS_jmpBoot[3];
@ -89,7 +93,7 @@ struct fat32_bpb {
uint8_t Zeroes[420]; uint8_t Zeroes[420];
uint8_t Signature[2]; /* 0x55aa */ uint8_t Signature[2]; /* 0x55aa */
} __attribute__ ((packed)); };
union fat_bpb { union fat_bpb {
struct fat16_bpb fat16; struct fat16_bpb fat16;
@ -97,7 +101,7 @@ union fat_bpb {
}; };
struct dir_entry { struct dir_entry {
char DIR_Name[11]; unsigned char DIR_Name[11];
uint8_t DIR_Attr; uint8_t DIR_Attr;
uint8_t DIR_NTRes; uint8_t DIR_NTRes;
uint8_t DIR_CrtTimeTenth; uint8_t DIR_CrtTimeTenth;
@ -109,7 +113,7 @@ struct dir_entry {
uint16_t DIR_WrtDate; uint16_t DIR_WrtDate;
uint16_t DIR_FstClusLO; uint16_t DIR_FstClusLO;
uint32_t DIR_FileSize; uint32_t DIR_FileSize;
} __attribute__ ((packed)); };
struct longfn_entry { struct longfn_entry {
uint8_t LDIR_Ord; uint8_t LDIR_Ord;
@ -120,7 +124,7 @@ struct longfn_entry {
char LDIR_Name2[12]; char LDIR_Name2[12];
uint16_t LDIR_FstClusLO; uint16_t LDIR_FstClusLO;
char LDIR_Name3[4]; char LDIR_Name3[4];
} __attribute__ ((packed)); };
#define LAST_LONG_ENTRY 0x40 #define LAST_LONG_ENTRY 0x40
@ -140,6 +144,8 @@ struct FSInfo {
uint32_t FSI_Nxt_Free; uint32_t FSI_Nxt_Free;
uint8_t FSI_Reserved2[12]; uint8_t FSI_Reserved2[12];
uint8_t FSI_TrailSig[4]; /* 0x00 0x00 0x55 0xAA */ uint8_t FSI_TrailSig[4]; /* 0x00 0x00 0x55 0xAA */
} __attribute__ ((packed)); };
#pragma pack(pop)
#endif // DEVICEWRAPPERSTRUCTS_H #endif // DEVICEWRAPPERSTRUCTS_H

View file

@ -5,6 +5,8 @@
#include "downloadthread.h" #include "downloadthread.h"
#include "config.h" #include "config.h"
#include "devicewrapper.h"
#include "devicewrapperfatpartition.h"
#include "dependencies/mountutils/src/mountutils.hpp" #include "dependencies/mountutils/src/mountutils.hpp"
#include "dependencies/drivelist/src/drivelist.hpp" #include "dependencies/drivelist/src/drivelist.hpp"
#include <fstream> #include <fstream>
@ -724,6 +726,15 @@ void DownloadThread::_writeComplete()
emit finalizing(); emit finalizing();
if (!_config.isEmpty() || !_cmdline.isEmpty() || !_firstrun.isEmpty())
{
if (!_customizeImage())
{
_closeFiles();
return;
}
}
if (_firstBlock) if (_firstBlock)
{ {
qDebug() << "Writing first block (which we skipped at first)"; qDebug() << "Writing first block (which we skipped at first)";
@ -741,6 +752,21 @@ void DownloadThread::_writeComplete()
_firstBlock = nullptr; _firstBlock = nullptr;
} }
if (!_file.flush())
{
DownloadThread::_onDownloadError(tr("Error writing to storage (while flushing)"));
_closeFiles();
return;
}
#ifndef Q_OS_WIN
if (::fsync(_file.handle()) != 0) {
DownloadThread::_onDownloadError(tr("Error writing to storage (while fsync)"));
_closeFiles();
return;
}
#endif
_closeFiles(); _closeFiles();
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
@ -748,17 +774,8 @@ void DownloadThread::_writeComplete()
_filename.replace("/dev/rdisk", "/dev/disk"); _filename.replace("/dev/rdisk", "/dev/disk");
#endif #endif
if (_ejectEnabled && _config.isEmpty() && _cmdline.isEmpty() && _firstrun.isEmpty())
eject_disk(_filename.constData());
if (!_config.isEmpty() || !_cmdline.isEmpty() || !_firstrun.isEmpty())
{
if (!_customizeImage())
return;
if (_ejectEnabled) if (_ejectEnabled)
eject_disk(_filename.constData()); eject_disk(_filename.constData());
}
emit success(); emit success();
} }
@ -865,169 +882,29 @@ void DownloadThread::setImageCustomization(const QByteArray &config, const QByte
bool DownloadThread::_customizeImage() bool DownloadThread::_customizeImage()
{ {
QString folder;
std::vector<std::string> mountpoints;
QByteArray devlower = _filename.toLower();
emit preparationStatusUpdate(tr("Waiting for FAT partition to be mounted"));
#ifdef Q_OS_WIN
qDebug() << "Running diskpart rescan";
QProcess proc;
proc.setProcessChannelMode(proc.MergedChannels);
proc.start("diskpart");
proc.waitForStarted();
proc.write("rescan\r\n");
proc.closeWriteChannel();
proc.waitForFinished();
qDebug() << proc.readAll();
#endif
/* See if OS auto-mounted the device */
for (int tries = 0; tries < 3; tries++)
{
QThread::sleep(1);
auto l = Drivelist::ListStorageDevices();
for (const auto& i : l)
{
if (QByteArray::fromStdString(i.device).toLower() == devlower && i.mountpoints.size())
{
mountpoints = i.mountpoints;
break;
}
}
}
#ifdef Q_OS_WIN
if (mountpoints.empty() && !_nr.isEmpty()) {
qDebug() << "Windows did not assign drive letter automatically. Ask diskpart to do so manually.";
proc.start("diskpart");
proc.waitForStarted();
proc.write("select disk "+_nr+"\r\n"
"select partition 1\r\n"
"assign\r\n");
proc.closeWriteChannel();
proc.waitForFinished();
qDebug() << proc.readAll();
auto l = Drivelist::ListStorageDevices();
for (auto i : l)
{
if (QByteArray::fromStdString(i.device).toLower() == devlower && i.mountpoints.size())
{
mountpoints = i.mountpoints;
break;
}
}
}
#endif
#ifdef Q_OS_LINUX
bool manualmount = false;
if (mountpoints.empty())
{
/* Manually mount folder */
manualmount = true;
QByteArray fatpartition = _filename;
if (isdigit(fatpartition.at(fatpartition.length()-1)))
fatpartition += "p1";
else
fatpartition += "1";
if (::access(devlower.constData(), W_OK) != 0)
{
/* Not running as root, try to outsource mounting to udisks2 */
#ifndef QT_NO_DBUS
UDisks2Api udisks2;
QString mp = udisks2.mountDevice(fatpartition);
if (!mp.isEmpty())
mountpoints.push_back(mp.toStdString());
#endif
}
else
{
/* Running as root, attempt running mount directly */
QTemporaryDir td;
QStringList args;
mountpoints.push_back(td.path().toStdString());
args << "-t" << "vfat" << fatpartition << td.path();
if (QProcess::execute("mount", args) != 0)
{
emit error(tr("Error mounting FAT32 partition"));
return false;
}
td.setAutoRemove(false);
}
}
#endif
if (mountpoints.empty())
{
//
qDebug() << "drive info. searching for:" << devlower;
auto l = Drivelist::ListStorageDevices();
for (const auto& i : l)
{
qDebug() << "drive" << QByteArray::fromStdString(i.device).toLower();
for (const auto& mp : i.mountpoints) {
qDebug() << "mountpoint:" << QByteArray::fromStdString(mp);
}
}
//
emit error(tr("Operating system did not mount FAT32 partition"));
return false;
}
/* Some operating system take longer to complete mounting FAT32
wait up to 3 seconds for config.txt file to appear */
QString configFilename;
bool foundFile = false;
for (int tries = 0; tries < 3; tries++)
{
/* Search all mountpoints, as on some systems FAT partition
may not be first volume */
for (const auto& mp : mountpoints)
{
folder = QString::fromStdString(mp);
if (folder.right(1) == '\\')
folder.chop(1);
configFilename = folder+"/config.txt";
if (QFile::exists(configFilename))
{
foundFile = true;
break;
}
}
if (foundFile)
break;
QThread::sleep(1);
}
if (!foundFile)
{
emit error(tr("Unable to customize. File '%1' does not exist.").arg(configFilename));
return false;
}
emit preparationStatusUpdate(tr("Customizing image")); emit preparationStatusUpdate(tr("Customizing image"));
try
{
DeviceWrapper dw(&_file);
if (_firstBlock)
{
/* Outsource first block handling to DeviceWrapper.
It will still not actually be written out yet,
until we call sync(), and then it will
save the first 4k sector with MBR for last */
dw.pwrite(_firstBlock, _firstBlockSize, 0);
_bytesWritten += _firstBlockSize;
qFreeAligned(_firstBlock);
_firstBlock = nullptr;
}
DeviceWrapperFatPartition *fat = dw.fatPartition(1);
if (!_config.isEmpty()) if (!_config.isEmpty())
{ {
auto configItems = _config.split('\n'); auto configItems = _config.split('\n');
configItems.removeAll(""); configItems.removeAll("");
QByteArray config; QByteArray config = fat->readFile("config.txt");
QFile f(configFilename);
if (f.open(f.ReadOnly))
{
config = f.readAll();
f.close();
}
for (const QByteArray& item : qAsConst(configItems)) for (const QByteArray& item : qAsConst(configItems))
{ {
@ -1045,30 +922,16 @@ bool DownloadThread::_customizeImage()
} }
} }
if (f.open(f.WriteOnly) && f.write(config) == config.length()) fat->writeFile("config.txt", config);
{
f.close();
}
else
{
emit error(tr("Error writing to config.txt on FAT partition"));
return false;
}
} }
if (_initFormat == "auto") if (_initFormat == "auto")
{ {
/* Do an attempt at auto-detecting what customization format a custom /* Do an attempt at auto-detecting what customization format a custom
image provided by the user supports */ image provided by the user supports */
QByteArray issue; QByteArray issue = fat->readFile("issue.txt");
QFile fi(folder+"/issue.txt");
if (fi.exists() && fi.open(fi.ReadOnly))
{
issue = fi.readAll();
fi.close();
}
if (QFile::exists(folder+"/user-data")) if (fat->fileExists("user-data"))
{ {
/* If we have user-data file on FAT partition, then it must be cloudinit */ /* If we have user-data file on FAT partition, then it must be cloudinit */
_initFormat = "cloudinit"; _initFormat = "cloudinit";
@ -1092,98 +955,38 @@ bool DownloadThread::_customizeImage()
if (!_firstrun.isEmpty() && _initFormat == "systemd") if (!_firstrun.isEmpty() && _initFormat == "systemd")
{ {
QFile f(folder+"/firstrun.sh"); fat->writeFile("firstrun.sh", _firstrun);
if (f.open(f.WriteOnly) && f.write(_firstrun) == _firstrun.length())
{
f.close();
}
else
{
emit error(tr("Error creating firstrun.sh on FAT partition"));
return false;
}
_cmdline += " systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target"; _cmdline += " systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target";
} }
if (!_cloudinit.isEmpty() && _initFormat == "cloudinit") if (!_cloudinit.isEmpty() && _initFormat == "cloudinit")
{ {
_cloudinit = "#cloud-config\n"+_cloudinit; _cloudinit = "#cloud-config\n"+_cloudinit;
QFile f(folder+"/user-data"); fat->writeFile("user-data", _cloudinit);
if (f.open(f.WriteOnly) && f.write(_cloudinit) == _cloudinit.length())
{
f.close();
}
else
{
emit error(tr("Error creating user-data cloudinit file on FAT partition"));
return false;
}
} }
if (!_cloudinitNetwork.isEmpty() && _initFormat == "cloudinit") if (!_cloudinitNetwork.isEmpty() && _initFormat == "cloudinit")
{ {
QFile f(folder+"/network-config"); fat->writeFile("network-config", _cloudinitNetwork);
if (f.open(f.WriteOnly) && f.write(_cloudinitNetwork) == _cloudinitNetwork.length())
{
f.close();
}
else
{
emit error(tr("Error creating network-config cloudinit file on FAT partition"));
return false;
}
} }
if (!_cmdline.isEmpty()) if (!_cmdline.isEmpty())
{ {
QByteArray cmdline; QByteArray cmdline = fat->readFile("cmdline.txt").trimmed();
QFile f(folder+"/cmdline.txt");
if (f.exists() && f.open(f.ReadOnly))
{
cmdline = f.readAll().trimmed();
f.close();
}
cmdline += _cmdline; cmdline += _cmdline;
if (f.open(f.WriteOnly) && f.write(cmdline) == cmdline.length())
{ fat->writeFile("cmdline.txt", cmdline);
f.close();
} }
else dw.sync();
}
catch (std::runtime_error &err)
{ {
emit error(tr("Error writing to cmdline.txt on FAT partition")); emit error(err.what());
return false; return false;
} }
}
emit finalizing(); emit finalizing();
#ifdef Q_OS_LINUX
if (manualmount)
{
if (::access(devlower.constData(), W_OK) != 0)
{
#ifndef QT_NO_DBUS
UDisks2Api udisks2;
udisks2.unmountDrive(devlower);
#endif
}
else
{
QStringList args;
args << folder;
QProcess::execute("umount", args);
QDir d;
d.rmdir(folder);
}
}
#endif
#ifndef Q_OS_WIN
::sync();
#endif
return true; return true;
} }