mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-19 00:15:21 +01:00
Move source files to /src
This commit is contained in:
parent
4daff1ba79
commit
033ff07abf
2685 changed files with 9 additions and 7 deletions
153
src/linux/linuxdrivelist.cpp
Normal file
153
src/linux/linuxdrivelist.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2020 Raspberry Pi Ltd
|
||||
*/
|
||||
|
||||
#include "../dependencies/drivelist/src/drivelist.hpp"
|
||||
#include <QProcess>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QDebug>
|
||||
|
||||
/*
|
||||
* Our third-party drivelist module does not provide a C++ implementation
|
||||
* for listing drives on Linux (only Javascript)
|
||||
* So roll our own for Linux using same function/data structure as the drivelist one
|
||||
*/
|
||||
|
||||
namespace Drivelist
|
||||
{
|
||||
static void _walkStorageChildren(Drivelist::DeviceDescriptor &d, QStringList &labels, QJsonArray &ca)
|
||||
{
|
||||
for (auto j : ca)
|
||||
{
|
||||
QJsonObject child = j.toObject();
|
||||
QString label = child["label"].toString();
|
||||
QString mp = child["mountpoint"].toString();
|
||||
if (!label.isEmpty())
|
||||
{
|
||||
labels.append(label);
|
||||
}
|
||||
if (!mp.isEmpty())
|
||||
{
|
||||
d.mountpoints.push_back(mp.toStdString());
|
||||
d.mountpointLabels.push_back(label.toStdString());
|
||||
}
|
||||
|
||||
QJsonArray subca = child["children"].toArray();
|
||||
if (subca.count())
|
||||
{
|
||||
_walkStorageChildren(d, labels, subca);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Drivelist::DeviceDescriptor> ListStorageDevices()
|
||||
{
|
||||
std::vector<DeviceDescriptor> deviceList;
|
||||
|
||||
QProcess p;
|
||||
QStringList args = { "--bytes", "--json", "--paths", "--output-all" };
|
||||
p.start("lsblk", args);
|
||||
p.waitForFinished(2000);
|
||||
QByteArray output = p.readAll();
|
||||
|
||||
if (p.exitStatus() != QProcess::NormalExit || p.exitCode() || output.isEmpty())
|
||||
{
|
||||
qDebug() << "Error executing lsblk";
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
QJsonDocument d = QJsonDocument::fromJson(output);
|
||||
QJsonArray a = d.object()["blockdevices"].toArray();
|
||||
for (auto i : a)
|
||||
{
|
||||
DeviceDescriptor d;
|
||||
QJsonObject bdev = i.toObject();
|
||||
QString name = bdev["kname"].toString();
|
||||
QString subsystems = bdev["subsystems"].toString();
|
||||
if (name.startsWith("/dev/loop") || name.startsWith("/dev/sr") || name.startsWith("/dev/ram") || name.startsWith("/dev/zram") || name.isEmpty())
|
||||
continue;
|
||||
|
||||
d.busType = bdev["busType"].toString().toStdString();
|
||||
d.device = name.toStdString();
|
||||
d.raw = true;
|
||||
d.isVirtual = subsystems == "block";
|
||||
if (bdev["ro"].isBool())
|
||||
{
|
||||
/* With some lsblk versions it is a bool in others a "0" or "1" string */
|
||||
d.isReadOnly = bdev["ro"].toBool();
|
||||
d.isRemovable= bdev["rm"].toBool() || bdev["hotplug"].toBool() || d.isVirtual;
|
||||
}
|
||||
else
|
||||
{
|
||||
d.isReadOnly = bdev["ro"].toString() == "1";
|
||||
d.isRemovable= bdev["rm"].toString() == "1" || bdev["hotplug"].toString() == "1" || d.isVirtual;
|
||||
}
|
||||
if (bdev["size"].isString())
|
||||
{
|
||||
d.size = bdev["size"].toString().toULongLong();
|
||||
}
|
||||
else
|
||||
{
|
||||
d.size = bdev["size"].toDouble();
|
||||
}
|
||||
d.isSystem = !d.isRemovable && !d.isVirtual;
|
||||
d.isUSB = subsystems.contains("usb");
|
||||
d.isSCSI = subsystems.contains("scsi") && !d.isUSB;
|
||||
d.blockSize = bdev["phy-sec"].toInt();
|
||||
d.logicalBlockSize = bdev["log-sec"].toInt();
|
||||
|
||||
QStringList dp = {
|
||||
bdev["label"].toString().trimmed(),
|
||||
bdev["vendor"].toString().trimmed(),
|
||||
bdev["model"].toString().trimmed()
|
||||
};
|
||||
if (name == "/dev/mmcblk0")
|
||||
{
|
||||
dp.removeAll("");
|
||||
if (dp.empty())
|
||||
dp.append(QObject::tr("Internal SD card reader"));
|
||||
}
|
||||
|
||||
QString mp = bdev["mountpoint"].toString();
|
||||
if (!mp.isEmpty())
|
||||
{
|
||||
d.mountpoints.push_back(mp.toStdString());
|
||||
d.mountpointLabels.push_back(bdev["label"].toString().toStdString());
|
||||
}
|
||||
QStringList labels;
|
||||
QJsonArray ca = bdev["children"].toArray();
|
||||
_walkStorageChildren(d, labels, ca);
|
||||
|
||||
if (labels.count()) {
|
||||
dp.append("("+labels.join(", ")+")");
|
||||
}
|
||||
dp.removeAll("");
|
||||
d.description = dp.join(" ").toStdString();
|
||||
|
||||
/* Mark internal NVMe drives as non-system if not mounted
|
||||
anywhere else than under /media */
|
||||
if (d.isSystem && subsystems.contains("nvme"))
|
||||
{
|
||||
bool isMounted = false;
|
||||
for (std::string mp : d.mountpoints)
|
||||
{
|
||||
if (!QByteArray::fromStdString(mp).startsWith("/media/")) {
|
||||
isMounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isMounted)
|
||||
{
|
||||
d.isSystem = false;
|
||||
}
|
||||
}
|
||||
|
||||
deviceList.push_back(d);
|
||||
}
|
||||
|
||||
return deviceList;
|
||||
}
|
||||
}
|
9
src/linux/rpi-imager.desktop
Normal file
9
src/linux/rpi-imager.desktop
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=Imager
|
||||
Comment=Raspberry Pi Imager
|
||||
Icon=rpi-imager
|
||||
Exec=rpi-imager
|
||||
Categories=Utility
|
||||
StartupNotify=false
|
63
src/linux/rpi-imager.metainfo.xml.in
Normal file
63
src/linux/rpi-imager.metainfo.xml.in
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<component type="desktop-application">
|
||||
<id>org.raspberrypi.rpi-imager</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>Apache-2.0</project_license>
|
||||
<name>Raspberry Pi Imager</name>
|
||||
<summary>Raspberry Pi imaging utility</summary>
|
||||
<description>
|
||||
<p>
|
||||
Raspberry Pi Imager downloads a .JSON file from the Raspberry Pi
|
||||
website with a list of all current download options, ensuring you are
|
||||
always installing the most up-to-date version.
|
||||
</p>
|
||||
<p>
|
||||
Once you’ve selected an operating system from the available options,
|
||||
the utility reads the relevant file directly from the Raspberry Pi
|
||||
website and writes it straight to the SD card. This speeds up the
|
||||
process quite considerably compared to the standard process of reading
|
||||
it from the website, writing it to a file on your hard drive, and then,
|
||||
as a separate step, reading it back from the hard drive and writing it
|
||||
to the SD card.
|
||||
</p>
|
||||
<p>
|
||||
During this process, Raspberry Pi Imager also caches the downloaded
|
||||
operating system image – that is to say, it saves a local copy on your
|
||||
computer, so you can program additional SD cards without having to
|
||||
download the file again.
|
||||
</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">rpi-imager.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>http://downloads.raspberrypi.org/imager/IMAGING-UTILITY-MAIN.png</image>
|
||||
<caption>Main window</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>http://downloads.raspberrypi.org/imager/IMAGING-UTILITY-OS.png</image>
|
||||
<caption>Choose OS</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>http://downloads.raspberrypi.org/imager/IMAGING-UTILITY-SD.png</image>
|
||||
<caption>Choose SD</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>http://downloads.raspberrypi.org/imager/IMAGING-UTILITY-WRITE.png</image>
|
||||
<caption>Write in progress</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>http://downloads.raspberrypi.org/imager/IMAGING-UTILITY-DONE.png</image>
|
||||
<caption>Write done</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://github.com/raspberrypi/rpi-imager</url>
|
||||
<provides>
|
||||
<binary>rpi-imager</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="@IMAGER_VERSION_STR@" />
|
||||
</releases>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-info">moderate</content_attribute>
|
||||
</content_rating>
|
||||
</component>
|
277
src/linux/udisks2api.cpp
Normal file
277
src/linux/udisks2api.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2020 Raspberry Pi Ltd
|
||||
*/
|
||||
|
||||
#include "udisks2api.h"
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusReply>
|
||||
#include <QDBusUnixFileDescriptor>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
UDisks2Api::UDisks2Api(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int UDisks2Api::authOpen(const QString &device, const QString &mode)
|
||||
{
|
||||
QString devpath = _resolveDevice(device);
|
||||
if (devpath.isEmpty())
|
||||
return -1;
|
||||
|
||||
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpath,
|
||||
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
|
||||
QString drive = blockdevice.property("Drive").value<QDBusObjectPath>().path();
|
||||
if (!drive.isEmpty() && drive != "/")
|
||||
{
|
||||
_unmountDrive(drive);
|
||||
}
|
||||
|
||||
// User may need to enter password in authentication dialog so set long timeout
|
||||
blockdevice.setTimeout(3600 * 1000);
|
||||
QVariantMap options = {{"flags", O_EXCL}};
|
||||
QDBusReply<QDBusUnixFileDescriptor> dbusfd = blockdevice.call("OpenDevice", mode, options);
|
||||
|
||||
if (!blockdevice.isValid() || !dbusfd.isValid() || !dbusfd.value().isValid())
|
||||
return -1;
|
||||
|
||||
int fd = ::dup(dbusfd.value().fileDescriptor());
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
QString UDisks2Api::_resolveDevice(const QString &device)
|
||||
{
|
||||
QDBusInterface manager("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/Manager",
|
||||
"org.freedesktop.UDisks2.Manager", QDBusConnection::systemBus());
|
||||
QVariantMap devspec = {{"path", device}};
|
||||
QVariantMap options;
|
||||
|
||||
QDBusReply<QList<QDBusObjectPath>> list = manager.call("ResolveDevice", devspec, options);
|
||||
|
||||
if (!manager.isValid() || !list.isValid() || list.value().isEmpty())
|
||||
return QString();
|
||||
|
||||
return list.value().first().path();
|
||||
}
|
||||
|
||||
void UDisks2Api::_unmountDrive(const QString &driveDbusPath)
|
||||
{
|
||||
//qDebug() << "Drive:" << driveDbusPath;
|
||||
|
||||
QDBusInterface manager("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/Manager",
|
||||
"org.freedesktop.UDisks2.Manager", QDBusConnection::systemBus());
|
||||
QVariantMap options;
|
||||
QDBusReply<QList<QDBusObjectPath>> list = manager.call("GetBlockDevices", options);
|
||||
|
||||
if (!manager.isValid() || !list.isValid())
|
||||
return;
|
||||
|
||||
for (auto devpath : list.value())
|
||||
{
|
||||
QString devpathStr = devpath.path();
|
||||
|
||||
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpathStr,
|
||||
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
|
||||
QString driveOfDev = blockdevice.property("Drive").value<QDBusObjectPath>().path();
|
||||
if (driveOfDev != driveDbusPath)
|
||||
continue;
|
||||
|
||||
//qDebug() << "Device:" << devpathStr << "belongs to same drive";
|
||||
QDBusInterface filesystem("org.freedesktop.UDisks2", devpathStr,
|
||||
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
|
||||
|
||||
QDBusReply<void> reply = filesystem.call("Unmount", options);
|
||||
if (reply.isValid())
|
||||
qDebug() << "Unmounted" << devpathStr << "successfully";
|
||||
}
|
||||
}
|
||||
|
||||
bool UDisks2Api::formatDrive(const QString &device, bool mountAfterwards)
|
||||
{
|
||||
QString devpath = _resolveDevice(device);
|
||||
if (devpath.isEmpty())
|
||||
return false;
|
||||
|
||||
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpath,
|
||||
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
|
||||
|
||||
QString drive = blockdevice.property("Drive").value<QDBusObjectPath>().path();
|
||||
if (!drive.isEmpty() && drive != "/")
|
||||
{
|
||||
_unmountDrive(drive);
|
||||
}
|
||||
|
||||
qDebug() << "Repartitioning drive";
|
||||
QVariantMap options;
|
||||
QDBusReply<void> reply = blockdevice.call("Format", "dos", options);
|
||||
if (!reply.isValid())
|
||||
{
|
||||
qDebug() << "Error repartitioning device:" << reply.error().message();
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap partOptions, formatOptions;
|
||||
QDBusInterface partitiontable("org.freedesktop.UDisks2", devpath,
|
||||
"org.freedesktop.UDisks2.PartitionTable", QDBusConnection::systemBus());
|
||||
|
||||
/* The all-in-one CreatePartitionAndFormat udisks2 method seems to not always
|
||||
work properly. Do seperate actions with sleep in between instead */
|
||||
qDebug() << "Adding partition";
|
||||
QDBusReply<QDBusObjectPath> newpartition = partitiontable.call("CreatePartition", QVariant((qulonglong) 4*1024*1024), QVariant((qulonglong) 0), "0x0e", "", partOptions);
|
||||
if (!newpartition.isValid())
|
||||
{
|
||||
qDebug() << "Error adding partition:" << newpartition.error().message();
|
||||
return false;
|
||||
}
|
||||
qDebug() << "New partition:" << newpartition.value().path();
|
||||
QThread::sleep(1);
|
||||
if (!drive.isEmpty() && drive != "/")
|
||||
{
|
||||
/* Unmount one more time, as auto-mount may have tried to mount an old FAT filesystem again if it
|
||||
* lives at the same sector in the new partition table as before */
|
||||
_unmountDrive(drive);
|
||||
}
|
||||
qDebug() << "Formatting drive as FAT32";
|
||||
QDBusInterface newblockdevice("org.freedesktop.UDisks2", newpartition.value().path(),
|
||||
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
|
||||
newblockdevice.setTimeout(300 * 1000);
|
||||
QDBusReply<void> fatformatreply = newblockdevice.call("Format", "vfat", formatOptions);
|
||||
if (!fatformatreply.isValid())
|
||||
{
|
||||
qDebug() << "Error from udisks2 while performing FAT32 format:" << fatformatreply.error().message();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mountAfterwards)
|
||||
{
|
||||
QDBusInterface filesystem("org.freedesktop.UDisks2", newpartition.value().path(),
|
||||
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
|
||||
QVariantMap mountOptions;
|
||||
|
||||
for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
qDebug() << "Mounting partition";
|
||||
// User may need to enter password in authentication dialog if non-removable storage, so set long timeout
|
||||
filesystem.setTimeout(3600 * 1000);
|
||||
QDBusReply<QString> mp = filesystem.call("Mount", mountOptions);
|
||||
|
||||
if (mp.isValid())
|
||||
{
|
||||
qDebug() << "Mounted new file system at:" << mp;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check if already auto-mounted */
|
||||
auto mps = mountPoints(filesystem);
|
||||
if (!mps.isEmpty())
|
||||
{
|
||||
qDebug() << "Was already auto-mounted at:" << mps;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Error mounting:" << mp.error().message();
|
||||
}
|
||||
}
|
||||
|
||||
QThread::sleep(1);
|
||||
}
|
||||
|
||||
qDebug() << "Failed to mount new file system.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString UDisks2Api::mountDevice(const QString &device)
|
||||
{
|
||||
QString devpath = _resolveDevice(device);
|
||||
if (devpath.isEmpty())
|
||||
return QString();
|
||||
|
||||
QDBusInterface filesystem("org.freedesktop.UDisks2", devpath,
|
||||
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
|
||||
QVariantMap mountOptions;
|
||||
|
||||
for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
qDebug() << "Mounting partition";
|
||||
// User may need to enter password in authentication dialog if non-removable storage, so set long timeout
|
||||
filesystem.setTimeout(3600 * 1000);
|
||||
QDBusReply<QString> mp = filesystem.call("Mount", mountOptions);
|
||||
|
||||
if (mp.isValid())
|
||||
{
|
||||
qDebug() << "Mounted file system at:" << mp;
|
||||
return mp;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check if already auto-mounted */
|
||||
auto mps = mountPoints(filesystem);
|
||||
if (!mps.isEmpty())
|
||||
{
|
||||
qDebug() << "Was already auto-mounted at:" << mps;
|
||||
return mps.first();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Error mounting:" << mp.error().message();
|
||||
}
|
||||
}
|
||||
|
||||
QThread::sleep(1);
|
||||
}
|
||||
|
||||
qDebug() << "Failed to mount file system.";
|
||||
return QString();
|
||||
}
|
||||
|
||||
void UDisks2Api::unmountDrive(const QString &device)
|
||||
{
|
||||
QString devpath = _resolveDevice(device);
|
||||
if (devpath.isEmpty())
|
||||
return;
|
||||
|
||||
_unmountDrive(devpath);
|
||||
}
|
||||
|
||||
QByteArrayList UDisks2Api::mountPoints(const QString &partitionDevice)
|
||||
{
|
||||
QString devpath = _resolveDevice(partitionDevice);
|
||||
if (devpath.isEmpty())
|
||||
return QByteArrayList();
|
||||
|
||||
QDBusInterface filesystem("org.freedesktop.UDisks2", devpath,
|
||||
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
|
||||
return mountPoints(filesystem);
|
||||
}
|
||||
|
||||
QByteArrayList UDisks2Api::mountPoints(const QDBusInterface &filesystem)
|
||||
{
|
||||
QByteArrayList mps;
|
||||
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.UDisks2", filesystem.path(),
|
||||
"org.freedesktop.DBus.Properties", "Get");
|
||||
QVariantList args = {"org.freedesktop.UDisks2.Filesystem", "MountPoints"};
|
||||
msg.setArguments(args);
|
||||
QDBusMessage reply = QDBusConnection::systemBus().call(msg);
|
||||
for (auto arg : reply.arguments())
|
||||
{
|
||||
arg.value<QDBusVariant>().variant().value<QDBusArgument>() >> mps;
|
||||
}
|
||||
for (auto &str : mps)
|
||||
{
|
||||
if (!str.isEmpty() && str.back() == '\0')
|
||||
str.chop(1);
|
||||
}
|
||||
|
||||
return mps;
|
||||
}
|
35
src/linux/udisks2api.h
Normal file
35
src/linux/udisks2api.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef UDISKS2API_H
|
||||
#define UDISKS2API_H
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2020 Raspberry Pi Ltd
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
|
||||
class QDBusInterface;
|
||||
|
||||
class UDisks2Api : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UDisks2Api(QObject *parent = nullptr);
|
||||
int authOpen(const QString &device, const QString &mode = "rw");
|
||||
bool formatDrive(const QString &device, bool mountAfterwards = true);
|
||||
QString mountDevice(const QString &device);
|
||||
void unmountDrive(const QString &device);
|
||||
QByteArrayList mountPoints(const QString &partitionDevice);
|
||||
QByteArrayList mountPoints(const QDBusInterface &filesystem);
|
||||
|
||||
protected:
|
||||
QString _resolveDevice(const QString &device);
|
||||
void _unmountDrive(const QString &driveDbusPath);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // UDISKS2API_H
|
Loading…
Add table
Add a link
Reference in a new issue