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

View file

@ -5,6 +5,8 @@
#include "downloadthread.h"
#include "config.h"
#include "devicewrapper.h"
#include "devicewrapperfatpartition.h"
#include "dependencies/mountutils/src/mountutils.hpp"
#include "dependencies/drivelist/src/drivelist.hpp"
#include <fstream>
@ -724,6 +726,15 @@ void DownloadThread::_writeComplete()
emit finalizing();
if (!_config.isEmpty() || !_cmdline.isEmpty() || !_firstrun.isEmpty())
{
if (!_customizeImage())
{
_closeFiles();
return;
}
}
if (_firstBlock)
{
qDebug() << "Writing first block (which we skipped at first)";
@ -741,6 +752,21 @@ void DownloadThread::_writeComplete()
_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();
#ifdef Q_OS_DARWIN
@ -748,18 +774,9 @@ void DownloadThread::_writeComplete()
_filename.replace("/dev/rdisk", "/dev/disk");
#endif
if (_ejectEnabled && _config.isEmpty() && _cmdline.isEmpty() && _firstrun.isEmpty())
if (_ejectEnabled)
eject_disk(_filename.constData());
if (!_config.isEmpty() || !_cmdline.isEmpty() || !_firstrun.isEmpty())
{
if (!_customizeImage())
return;
if (_ejectEnabled)
eject_disk(_filename.constData());
}
emit success();
}
@ -865,325 +882,111 @@ void DownloadThread::setImageCustomization(const QByteArray &config, const QByte
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"));
if (!_config.isEmpty())
try
{
auto configItems = _config.split('\n');
configItems.removeAll("");
QByteArray config;
QFile f(configFilename);
if (f.open(f.ReadOnly))
DeviceWrapper dw(&_file);
if (_firstBlock)
{
config = f.readAll();
f.close();
/* 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())
{
auto configItems = _config.split('\n');
configItems.removeAll("");
QByteArray config = fat->readFile("config.txt");
for (const QByteArray& item : qAsConst(configItems))
{
if (config.contains("#"+item)) {
/* Uncomment existing line */
config.replace("#"+item, item);
} else if (config.contains("\n"+item)) {
/* config.txt already contains the line */
} else {
/* Append new line to config.txt */
if (config.right(1) != "\n")
config += "\n"+item+"\n";
else
config += item+"\n";
}
}
fat->writeFile("config.txt", config);
}
for (const QByteArray& item : qAsConst(configItems))
if (_initFormat == "auto")
{
if (config.contains("#"+item)) {
/* Uncomment existing line */
config.replace("#"+item, item);
} else if (config.contains("\n"+item)) {
/* config.txt already contains the line */
} else {
/* Append new line to config.txt */
if (config.right(1) != "\n")
config += "\n"+item+"\n";
else
config += item+"\n";
/* Do an attempt at auto-detecting what customization format a custom
image provided by the user supports */
QByteArray issue = fat->readFile("issue.txt");
if (fat->fileExists("user-data"))
{
/* If we have user-data file on FAT partition, then it must be cloudinit */
_initFormat = "cloudinit";
qDebug() << "user-data found on FAT partition. Assuming cloudinit support";
}
else if (issue.contains("pi-gen"))
{
/* If issue.txt mentions pi-gen, and there is no user-data file assume
* it is a RPI OS flavor, and use the old systemd unit firstrun script stuff */
_initFormat = "systemd";
qDebug() << "using firstrun script invoked by systemd customization method";
}
else
{
/* Fallback to writing cloudinit file, as it does not hurt having one
* Will just have no customization if OS does not support it */
_initFormat = "cloudinit";
qDebug() << "Unknown what customization method image supports. Falling back to cloudinit";
}
}
if (f.open(f.WriteOnly) && f.write(config) == config.length())
if (!_firstrun.isEmpty() && _initFormat == "systemd")
{
f.close();
fat->writeFile("firstrun.sh", _firstrun);
_cmdline += " systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target";
}
else
if (!_cloudinit.isEmpty() && _initFormat == "cloudinit")
{
emit error(tr("Error writing to config.txt on FAT partition"));
return false;
_cloudinit = "#cloud-config\n"+_cloudinit;
fat->writeFile("user-data", _cloudinit);
}
if (!_cloudinitNetwork.isEmpty() && _initFormat == "cloudinit")
{
fat->writeFile("network-config", _cloudinitNetwork);
}
if (!_cmdline.isEmpty())
{
QByteArray cmdline = fat->readFile("cmdline.txt").trimmed();
cmdline += _cmdline;
fat->writeFile("cmdline.txt", cmdline);
}
dw.sync();
}
if (_initFormat == "auto")
catch (std::runtime_error &err)
{
/* Do an attempt at auto-detecting what customization format a custom
image provided by the user supports */
QByteArray issue;
QFile fi(folder+"/issue.txt");
if (fi.exists() && fi.open(fi.ReadOnly))
{
issue = fi.readAll();
fi.close();
}
if (QFile::exists(folder+"/user-data"))
{
/* If we have user-data file on FAT partition, then it must be cloudinit */
_initFormat = "cloudinit";
qDebug() << "user-data found on FAT partition. Assuming cloudinit support";
}
else if (issue.contains("pi-gen"))
{
/* If issue.txt mentions pi-gen, and there is no user-data file assume
* it is a RPI OS flavor, and use the old systemd unit firstrun script stuff */
_initFormat = "systemd";
qDebug() << "using firstrun script invoked by systemd customization method";
}
else
{
/* Fallback to writing cloudinit file, as it does not hurt having one
* Will just have no customization if OS does not support it */
_initFormat = "cloudinit";
qDebug() << "Unknown what customization method image supports. Falling back to cloudinit";
}
}
if (!_firstrun.isEmpty() && _initFormat == "systemd")
{
QFile f(folder+"/firstrun.sh");
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";
}
if (!_cloudinit.isEmpty() && _initFormat == "cloudinit")
{
_cloudinit = "#cloud-config\n"+_cloudinit;
QFile f(folder+"/user-data");
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")
{
QFile f(folder+"/network-config");
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())
{
QByteArray cmdline;
QFile f(folder+"/cmdline.txt");
if (f.exists() && f.open(f.ReadOnly))
{
cmdline = f.readAll().trimmed();
f.close();
}
cmdline += _cmdline;
if (f.open(f.WriteOnly) && f.write(cmdline) == cmdline.length())
{
f.close();
}
else
{
emit error(tr("Error writing to cmdline.txt on FAT partition"));
return false;
}
emit error(err.what());
return false;
}
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;
}