File indexing completed on 2024-04-21 05:41:03
0001 /* 0002 SPDX-FileCopyrightText: 2020 Kwon-Young Choi <kwon-young.choi@hotmail.fr> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "mountisoaction.h" 0008 0009 #include <fcntl.h> 0010 #include <unistd.h> 0011 #include <string.h> 0012 #include <sys/stat.h> 0013 #include <sys/types.h> 0014 #include <errno.h> 0015 0016 #include <QAction> 0017 #include <QDBusConnection> 0018 #include <QDBusInterface> 0019 #include <QDBusReply> 0020 #include <QDBusUnixFileDescriptor> 0021 #include <QDebug> 0022 #include <QEventLoop> 0023 #include <QIcon> 0024 #include <QMap> 0025 #include <QObject> 0026 #include <QProcess> 0027 #include <QString> 0028 #include <QTimer> 0029 #include <QVariant> 0030 #include <QWidget> 0031 0032 #include <KLocalizedString> 0033 #include <KPluginFactory> 0034 #include <Solid/Block> 0035 #include <Solid/Device> 0036 #include <Solid/DeviceInterface> 0037 #include <Solid/DeviceNotifier> 0038 #include <Solid/GenericInterface> 0039 #include <Solid/StorageAccess> 0040 #include <Solid/StorageVolume> 0041 0042 K_PLUGIN_CLASS_WITH_JSON(MountIsoAction, "mountisoaction.json") 0043 0044 MountIsoAction::MountIsoAction(QObject *parent, const QVariantList &) 0045 : KAbstractFileItemActionPlugin(parent) 0046 { 0047 } 0048 0049 /** 0050 * Get block device udi ("/org/freedesktop/UDisks2/block_devices/loop0") using 0051 * its backing file name. 0052 * 0053 * Use the Solid framework to iterate through all block devices to check if the 0054 * backing file correspond to the given backingFile. 0055 * 0056 * Warning: The use of GenericInterface makes this function non portable, 0057 * especially to non Unix-like OS. 0058 * 0059 * @backingFile: backing file of the device we want. 0060 * 0061 * @return: device udi of the found block device. If no corresponding device 0062 * was found, return a null QString. 0063 */ 0064 const Solid::Device getDeviceFromBackingFile(const QString &backingFile) 0065 { 0066 const QList<Solid::Device> blockDevices = 0067 Solid::Device::listFromQuery(QStringLiteral("[ IS StorageVolume AND IS GenericInterface ]")); 0068 0069 for (const Solid::Device &device : blockDevices) { 0070 auto genericDevice = device.as<Solid::GenericInterface>(); 0071 if (backingFile == genericDevice->property(QStringLiteral("BackingFile")).toString()) { 0072 return device; 0073 } 0074 } 0075 return Solid::Device(); 0076 } 0077 0078 const QList<Solid::Device> getStorageAccessFromDevice(const Solid::Device &device) 0079 { 0080 auto genericInterface = device.as<Solid::GenericInterface>(); 0081 // Solid always returns UUID lower-case 0082 const QString uuid = genericInterface->property(QStringLiteral("IdUUID")).value<QString>().toLower(); 0083 auto query = QStringLiteral("[ StorageVolume.uuid == '%1' AND IS StorageAccess ]").arg(uuid); 0084 return Solid::Device::listFromQuery(query); 0085 } 0086 0087 /** 0088 * Callback function for mounting an iso file as a loop device 0089 * 0090 * Uses UDisks2 Manager DBus api to mount the iso file 0091 * 0092 * @file: iso file path to mount 0093 */ 0094 void mount(const QString &file) 0095 { 0096 const int fd = open(file.toLocal8Bit().data(), O_RDONLY); 0097 if (fd == -1) { 0098 qWarning() << "Error opening " << file << ": " << strerror(errno); 0099 return; 0100 } 0101 auto qtFd = QDBusUnixFileDescriptor(fd); 0102 int res = close(fd); 0103 if (res == -1) { 0104 qWarning() << "Error closing " << file << ": " << strerror(errno); 0105 return; 0106 } 0107 QMap<QString, QVariant> options; 0108 0109 QDBusInterface manager( 0110 QStringLiteral("org.freedesktop.UDisks2"), 0111 QStringLiteral("/org/freedesktop/UDisks2/Manager"), 0112 QStringLiteral("org.freedesktop.UDisks2.Manager"), 0113 QDBusConnection::systemBus()); 0114 QDBusReply<QDBusObjectPath> reply = 0115 manager.call(QStringLiteral("LoopSetup"), QVariant::fromValue(qtFd), options); 0116 0117 if (!reply.isValid()) { 0118 qWarning() << "Error mounting " << file << ":" << reply.error().name() 0119 << reply.error().message(); 0120 return; 0121 } 0122 0123 // Need to wait for UDisks2 to send a signal to Solid to update its database 0124 auto notifier = Solid::DeviceNotifier::instance(); 0125 0126 // The following code can not be put into a slot because the MountIsoAction object is destroyed 0127 // as soon as this function ends 0128 QEventLoop loop; 0129 QTimer timer; 0130 timer.setSingleShot(true); 0131 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); 0132 QObject::connect(notifier, &Solid::DeviceNotifier::deviceAdded, &loop, &QEventLoop::quit); 0133 0134 int i = 0, maxDeviceRace = 4; 0135 Solid::Device device; 0136 while (i < maxDeviceRace) { 0137 timer.start(5000); // 5s timeout 0138 loop.exec(); 0139 0140 device = Solid::Device(reply.value().path()); 0141 if (!device.is<Solid::StorageVolume>()) { 0142 i++; 0143 } else { 0144 break; 0145 } 0146 } 0147 0148 if (i == maxDeviceRace) { 0149 // Something really wrong happened 0150 return; 0151 } 0152 0153 auto storageVolume = device.as<Solid::StorageVolume>(); 0154 const QString uuid = storageVolume->uuid(); 0155 0156 const QList<Solid::Device> devices = Solid::Device::listFromQuery( 0157 QStringLiteral("[ StorageVolume.uuid == '%1' AND IS StorageAccess ]").arg(uuid)); 0158 for (auto dev : devices) { 0159 auto storageAccess = dev.as<Solid::StorageAccess>(); 0160 storageAccess->setup(); 0161 } 0162 } 0163 0164 /** 0165 * Callback function for deleting a loop device 0166 * 0167 * Uses UDisks2 DBus api to delete a loop device 0168 * 0169 * @file: iso file to mount 0170 */ 0171 void unmount(const Solid::Device &device) 0172 { 0173 const QList<Solid::Device> devices = getStorageAccessFromDevice(device); 0174 for (Solid::Device storageAccessDevice : devices) { 0175 auto storageAccess = storageAccessDevice.as<Solid::StorageAccess>(); 0176 if (storageAccess->isAccessible()) { 0177 storageAccess->teardown(); 0178 } 0179 } 0180 0181 // Empty argument required for Loop Delete method to work 0182 QMap<QString, QVariant> options; 0183 0184 QDBusInterface manager( 0185 QStringLiteral("org.freedesktop.UDisks2"), 0186 device.udi(), 0187 QStringLiteral("org.freedesktop.UDisks2.Loop"), 0188 QDBusConnection::systemBus()); 0189 manager.call(QStringLiteral("Delete"), options); 0190 } 0191 0192 QList<QAction *> MountIsoAction::actions(const KFileItemListProperties &fileItemInfos, 0193 QWidget *parentWidget) 0194 { 0195 if (fileItemInfos.urlList().size() != 1 || !fileItemInfos.isLocal()) { 0196 return {}; 0197 }; 0198 0199 const QString mimeType = fileItemInfos.mimeType(); 0200 0201 if (mimeType != QLatin1String("application/vnd.efi.iso") 0202 && mimeType != QLatin1String("application/vnd.efi.img") 0203 && mimeType != QLatin1String("application/x-cd-image") 0204 && mimeType != QLatin1String("application/x-raw-disk-image")) { 0205 return {}; 0206 } 0207 0208 auto file = fileItemInfos.urlList().at(0).toLocalFile(); 0209 0210 // Check if dbus can handle file descriptor 0211 auto connection = QDBusConnection::sessionBus(); 0212 QDBusConnection::ConnectionCapabilities capabilities = connection.connectionCapabilities(); 0213 if (!(capabilities & QDBusConnection::UnixFileDescriptorPassing)) { 0214 return {}; 0215 } 0216 0217 const Solid::Device device = getDeviceFromBackingFile(file); 0218 0219 if (!device.isValid()) { 0220 const QIcon icon = QIcon::fromTheme(QStringLiteral("media-mount")); 0221 const QString title = i18nc("@action:inmenu Action to mount a disk image", "Mount"); 0222 0223 QAction *action = new QAction(icon, title, parentWidget); 0224 0225 connect(action, &QAction::triggered, this, [file]() { mount(file); }); 0226 return { action }; 0227 } else { 0228 // fileItem is mounted on device 0229 const QIcon icon = QIcon::fromTheme(QStringLiteral("media-eject")); 0230 const QString title = i18nc("@action:inmenu Action to unmount a disk image", "Unmount"); 0231 0232 QAction *action = new QAction(icon, title, parentWidget); 0233 0234 connect(action, &QAction::triggered, this, [device]() { unmount(device); }); 0235 return { action }; 0236 } 0237 0238 return {}; 0239 } 0240 0241 #include "mountisoaction.moc" 0242 0243 #include "moc_mountisoaction.cpp"