/* Copyright 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "halstorageaccess.h" #include "halfstabhandling.h" #include "../../genericinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_FREEBSD #include #endif using namespace Solid::Backends::Hal; StorageAccess::StorageAccess(HalDevice *device) : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_ejectInProgress(false), m_passphraseRequested(false) { connect(device, SIGNAL(propertyChanged(QMap)), this, SLOT(slotPropertyChanged(QMap))); // Delay connecting to DBus signals to avoid the related time penalty // in hot paths such as predicate matching QTimer::singleShot(0, this, SLOT(connectDBusSignals())); } StorageAccess::~StorageAccess() { } void StorageAccess::connectDBusSignals() { m_device->registerAction("setup", this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int,QString))); m_device->registerAction("teardown", this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int,QString))); m_device->registerAction("eject", this, SLOT(slotEjectRequested()), SLOT(slotEjectDone(int,QString))); } void StorageAccess::slotSetupDone(int error, const QString &errorString) { m_setupInProgress = false; emit setupDone(static_cast(error), errorString, m_device->udi()); } void StorageAccess::slotTeardownDone(int error, const QString &errorString) { m_teardownInProgress = false; emit teardownDone(static_cast(error), errorString, m_device->udi()); } void StorageAccess::slotEjectDone(int error, const QString &errorString) { m_ejectInProgress = false; emit ejectDone(static_cast(error), errorString, m_device->udi()); } bool StorageAccess::isAccessible() const { if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { // Might be a bit slow, but I see no cleaner way to do this with HAL... QDBusInterface manager("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", QDBusConnection::systemBus()); QDBusReply reply = manager.call("FindDeviceStringMatch", "volume.crypto_luks.clear.backing_volume", m_device->udi()); QStringList list = reply; return reply.isValid() && !list.isEmpty(); } else { return m_device->prop("volume.is_mounted").toBool(); } } QString StorageAccess::filePath() const { QString result = m_device->prop("volume.mount_point").toString(); if (result.isEmpty()) { QStringList mountpoints = FstabHandling::possibleMountPoints(m_device->prop("block.device").toString()); if (mountpoints.size()==1) { result = mountpoints.first(); } } return result; } bool StorageAccess::isIgnored() const { HalDevice lock("/org/freedesktop/Hal/devices/computer"); bool isLocked = lock.prop("info.named_locks.Global.org.freedesktop.Hal.Device.Storage.locked").toBool(); if (m_device->prop("volume.ignore").toBool() || isLocked ){ return true; } const QString mount_point = StorageAccess(m_device).filePath(); const bool mounted = m_device->prop("volume.is_mounted").toBool(); if (!mounted) { return false; } else if (mount_point.startsWith(QLatin1String("/media/")) || mount_point.startsWith(QLatin1String("/mnt/"))) { return false; } /* Now be a bit more aggressive on what we want to ignore, * the user generally need to check only what's removable or in /media * the volumes mounted to make the system (/, /boot, /var, etc.) * are useless to him. */ Solid::Device drive(m_device->prop("block.storage_device").toString()); const bool removable = drive.as()->property("storage.removable").toBool(); const bool hotpluggable = drive.as()->property("storage.hotpluggable").toBool(); return !removable && !hotpluggable; } bool StorageAccess::setup() { if (m_teardownInProgress || m_setupInProgress || isAccessible()) { return false; } m_setupInProgress = true; m_device->broadcastActionRequested("setup"); if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { return requestPassphrase(); } else if (FstabHandling::isInFstab(m_device->prop("block.device").toString())) { return callSystemMount(); } else { return callHalVolumeMount(); } } bool StorageAccess::teardown() { if (m_teardownInProgress || m_setupInProgress || !isAccessible()) { return false; } m_teardownInProgress = true; m_device->broadcastActionRequested("teardown"); if (m_device->prop("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) { return callCryptoTeardown(); } else if (FstabHandling::isInFstab(m_device->prop("block.device").toString())) { return callSystemUnmount(); } else { return callHalVolumeUnmount(); } } void StorageAccess::slotPropertyChanged(const QMap &changes) { if (changes.contains("volume.is_mounted")) { emit accessibilityChanged(isAccessible(), m_device->udi()); } } void StorageAccess::slotDBusReply(const QDBusMessage &/*reply*/) { if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown"); HalDevice drive(m_device->prop("block.storage_device").toString()); if (drive.prop("storage.drive_type").toString()!="cdrom" && drive.prop("storage.requires_eject").toBool()) { QString devnode = m_device->prop("block.device").toString(); #if defined(Q_OS_OPENBSD) QString program = "cdio"; QStringList args; args << "-f" << devnode << "eject"; #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) devnode.remove("/dev/").replace("([0-9]).", "\\1"); QString program = "cdcontrol"; QStringList args; args << "-f" << devnode << "eject"; #else QString program = "eject"; QStringList args; args << devnode; #endif m_ejectInProgress = true; m_device->broadcastActionRequested("eject"); m_process = FstabHandling::callSystemCommand("eject", args, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); } } else if (m_ejectInProgress) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } } void StorageAccess::slotDBusError(const QDBusError &error) { // TODO: Better error reporting here if (m_setupInProgress) { m_setupInProgress = false; m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } else if (m_teardownInProgress) { m_teardownInProgress = false; m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } else if (m_ejectInProgress) { m_ejectInProgress = false; m_device->broadcastActionDone("eject", Solid::UnauthorizedOperation, QString(error.name()+": "+error.message())); } } void Solid::Backends::Hal::StorageAccess::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (m_setupInProgress) { m_setupInProgress = false; if (exitCode==0) { m_device->broadcastActionDone("setup"); } else { m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation, m_process->readAllStandardError()); } } else if (m_teardownInProgress) { m_teardownInProgress = false; if (exitCode==0) { m_device->broadcastActionDone("teardown"); } else { m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation, m_process->readAllStandardError()); } } else if (m_ejectInProgress) { if (exitCode==0) { m_ejectInProgress = false; m_device->broadcastActionDone("eject"); } else { callHalVolumeEject(); } } delete m_process; } void StorageAccess::slotSetupRequested() { m_setupInProgress = true; emit setupRequested(m_device->udi()); } void StorageAccess::slotTeardownRequested() { m_teardownInProgress = true; emit teardownRequested(m_device->udi()); } void StorageAccess::slotEjectRequested() { m_ejectInProgress = true; } QString generateReturnObjectPath() { static int number = 1; return "/org/kde/solid/HalStorageAccess_"+QString::number(number++); } bool StorageAccess::requestPassphrase() { QString udi = m_device->udi(); QString returnService = QDBusConnection::sessionBus().baseService(); m_lastReturnObject = generateReturnObjectPath(); QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots); QWidget *activeWindow = QApplication::activeWindow(); uint wId = 0; if (activeWindow!=nullptr) { wId = (uint)activeWindow->winId(); } QString appId = QCoreApplication::applicationName(); QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer"); QDBusReply reply = soliduiserver.call("showPassphraseDialog", udi, returnService, m_lastReturnObject, wId, appId); m_passphraseRequested = reply.isValid(); if (!m_passphraseRequested) { qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error(); } return m_passphraseRequested; } void StorageAccess::passphraseReply(const QString &passphrase) { if (m_passphraseRequested) { QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject); m_passphraseRequested = false; if (!passphrase.isEmpty()) { callCryptoSetup(passphrase); } else { m_setupInProgress = false; m_device->broadcastActionDone("setup"); } } } bool StorageAccess::callHalVolumeMount() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume", "Mount"); // HAL 0.5.12 supports using alternative drivers for the same filesystem. // This is mainly used to integrate the ntfs-3g driver. // Unfortunately, the primary driver gets used unless we // specify some other driver (fstype) to the Mount method. // TODO: Allow the user to choose the driver. QString fstype = m_device->prop("volume.fstype").toString(); QStringList halOptions = m_device->prop("volume.mount.valid_options").toStringList(); QString alternativePreferred = m_device->prop("volume.fstype.alternative.preferred").toString(); if (!alternativePreferred.isEmpty()) { QStringList alternativeFstypes = m_device->prop("volume.fstype.alternative").toStringList(); if (alternativeFstypes.contains(alternativePreferred)) { fstype = alternativePreferred; halOptions = m_device->prop("volume.mount."+fstype+".valid_options").toStringList(); } } QStringList options; #ifdef Q_OS_FREEBSD QString uid="-u="; #else QString uid="uid="; #endif if (halOptions.contains(uid)) { options << uid+QString::number(::getuid()); } #ifdef Q_OS_FREEBSD char *cType; if ( fstype=="vfat" && halOptions.contains("-L=")) { if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) options << "-L="+QString(cType); } else if ( (fstype.startsWith(QLatin1String("ntfs")) || fstype=="iso9660" || fstype=="udf") && halOptions.contains("-C=") ) { if ((cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) options << "-C="+QString(nl_langinfo(CODESET)); } #else if (fstype=="vfat" || fstype=="ntfs" || fstype=="iso9660" || fstype=="udf" ) { if (halOptions.contains("utf8")) options<<"utf8"; else if (halOptions.contains("iocharset=")) options<<"iocharset=utf8"; if (halOptions.contains("shortname=")) options<<"shortname=mixed"; if (halOptions.contains("flush")) options<<"flush"; } // pass our locale to the ntfs-3g driver so it can translate local characters else if ( halOptions.contains("locale=") ) { // have to obtain LC_CTYPE as returned by the `locale` command // check in the same order as `locale` does char *cType; if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) { options << "locale="+QString(cType); } } #endif msg << "" << fstype << options; return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callHalVolumeUnmount() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume", "Unmount"); msg << QStringList(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callHalVolumeEject() { QString udi = m_device->udi(); QString interface = "org.freedesktop.Hal.Device.Volume"; QDBusConnection c = QDBusConnection::systemBus(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, interface, "Eject"); msg << QStringList(); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool Solid::Backends::Hal::StorageAccess::callSystemMount() { const QString device = m_device->prop("block.device").toString(); m_process = FstabHandling::callSystemCommand("mount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=nullptr; } bool Solid::Backends::Hal::StorageAccess::callSystemUnmount() { const QString device = m_device->prop("block.device").toString(); m_process = FstabHandling::callSystemCommand("umount", device, this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); return m_process!=nullptr; } void StorageAccess::callCryptoSetup(const QString &passphrase) { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume.Crypto", "Setup"); msg << passphrase; c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } bool StorageAccess::callCryptoTeardown() { QDBusConnection c = QDBusConnection::systemBus(); QString udi = m_device->udi(); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi, "org.freedesktop.Hal.Device.Volume.Crypto", "Teardown"); return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError))); } #include "backends/hal/moc_halstorageaccess.cpp"