2
Copyright 2006 Kevin Ottens <ervin@kde.org>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Lesser General Public
6
License as published by the Free Software Foundation; either
7
version 2.1 of the License, or (at your option) version 3, or any
8
later version accepted by the membership of KDE e.V. (or its
9
successor approved by the membership of KDE e.V.), which shall
10
act as a proxy defined in Section 6 of version 3 of the license.
12
This library is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
Lesser General Public License for more details.
17
You should have received a copy of the GNU Lesser General Public
18
License along with this library. If not, see <http://www.gnu.org/licenses/>.
21
#include "halstorageaccess.h"
23
#include "halfstabhandling.h"
24
#include "../../genericinterface.h"
26
#include <QtCore/QLocale>
27
#include <QtCore/QDebug>
28
#include <QtCore/QTextStream>
29
#include <QtCore/QProcess>
30
#include <QtCore/QTimer>
31
#include <QtDBus/QDBusConnection>
32
#include <QtDBus/QDBusInterface>
33
#include <QtDBus/QDBusReply>
34
#include <QtDBus/QDBusVariant>
35
#include <QtGui/QApplication>
36
#include <QtGui/QWidget>
37
#include <localization/klocalizedstring.h>
47
using namespace Solid::Backends::Hal;
49
StorageAccess::StorageAccess(HalDevice *device)
50
: DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_ejectInProgress(false),
51
m_passphraseRequested(false)
53
connect(device, SIGNAL(propertyChanged(const QMap<QString,int> &)),
54
this, SLOT(slotPropertyChanged(const QMap<QString,int> &)));
55
// Delay connecting to DBus signals to avoid the related time penalty
56
// in hot paths such as predicate matching
57
QTimer::singleShot(0, this, SLOT(connectDBusSignals()));
60
StorageAccess::~StorageAccess()
65
void StorageAccess::connectDBusSignals()
67
m_device->registerAction("setup", this,
68
SLOT(slotSetupRequested()),
69
SLOT(slotSetupDone(int, const QString&)));
71
m_device->registerAction("teardown", this,
72
SLOT(slotTeardownRequested()),
73
SLOT(slotTeardownDone(int, const QString&)));
75
m_device->registerAction("eject", this,
76
SLOT(slotEjectRequested()),
77
SLOT(slotEjectDone(int, const QString&)));
80
void StorageAccess::slotSetupDone(int error, const QString &errorString)
82
m_setupInProgress = false;
83
emit setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
86
void StorageAccess::slotTeardownDone(int error, const QString &errorString)
88
m_teardownInProgress = false;
89
emit teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
92
void StorageAccess::slotEjectDone(int error, const QString &errorString)
94
m_ejectInProgress = false;
95
emit ejectDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
98
bool StorageAccess::isAccessible() const
100
if (m_device->property("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) {
102
// Might be a bit slow, but I see no cleaner way to do this with HAL...
103
QDBusInterface manager("org.freedesktop.Hal",
104
"/org/freedesktop/Hal/Manager",
105
"org.freedesktop.Hal.Manager",
106
QDBusConnection::systemBus());
108
QDBusReply<QStringList> reply = manager.call("FindDeviceStringMatch",
109
"volume.crypto_luks.clear.backing_volume",
112
QStringList list = reply;
114
return reply.isValid() && !list.isEmpty();
117
return m_device->property("volume.is_mounted").toBool();
121
QString StorageAccess::filePath() const
123
QString result = m_device->property("volume.mount_point").toString();
125
if (result.isEmpty()) {
126
QStringList mountpoints
127
= FstabHandling::possibleMountPoints(m_device->property("block.device").toString());
128
if (mountpoints.size()==1) {
129
result = mountpoints.first();
136
bool StorageAccess::isIgnored() const
138
HalDevice lock("/org/freedesktop/Hal/devices/computer");
139
bool isLocked = lock.property("info.named_locks.Global.org.freedesktop.Hal.Device.Storage.locked").toBool();
141
if (m_device->property("volume.ignore").toBool() || isLocked ){
145
const QString mount_point = StorageAccess(m_device).filePath();
146
const bool mounted = m_device->property("volume.is_mounted").toBool();
149
} else if (mount_point.startsWith(QLatin1String("/media/")) || mount_point.startsWith(QLatin1String("/mnt/"))) {
153
/* Now be a bit more aggressive on what we want to ignore,
154
* the user generally need to check only what's removable or in /media
155
* the volumes mounted to make the system (/, /boot, /var, etc.)
156
* are useless to him.
158
Solid::Device drive(m_device->property("block.storage_device").toString());
160
const bool removable = drive.as<Solid::GenericInterface>()->property("storage.removable").toBool();
161
const bool hotpluggable = drive.as<Solid::GenericInterface>()->property("storage.hotpluggable").toBool();
163
return !removable && !hotpluggable;
166
bool StorageAccess::setup()
168
if (m_teardownInProgress || m_setupInProgress || isAccessible()) {
171
m_setupInProgress = true;
172
m_device->broadcastActionRequested("setup");
174
if (m_device->property("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) {
175
return requestPassphrase();
176
} else if (FstabHandling::isInFstab(m_device->property("block.device").toString())) {
177
return callSystemMount();
179
return callHalVolumeMount();
183
bool StorageAccess::teardown()
185
if (m_teardownInProgress || m_setupInProgress || !isAccessible()) {
188
m_teardownInProgress = true;
189
m_device->broadcastActionRequested("teardown");
191
if (m_device->property("info.interfaces").toStringList().contains("org.freedesktop.Hal.Device.Volume.Crypto")) {
192
return callCryptoTeardown();
193
} else if (FstabHandling::isInFstab(m_device->property("block.device").toString())) {
194
return callSystemUnmount();
196
return callHalVolumeUnmount();
200
void StorageAccess::slotPropertyChanged(const QMap<QString,int> &changes)
202
if (changes.contains("volume.is_mounted"))
204
emit accessibilityChanged(isAccessible(), m_device->udi());
208
void StorageAccess::slotDBusReply(const QDBusMessage &/*reply*/)
210
if (m_setupInProgress) {
211
m_setupInProgress = false;
212
m_device->broadcastActionDone("setup");
213
} else if (m_teardownInProgress) {
214
m_teardownInProgress = false;
215
m_device->broadcastActionDone("teardown");
217
HalDevice drive(m_device->property("block.storage_device").toString());
218
if (drive.property("storage.drive_type").toString()!="cdrom"
219
&& drive.property("storage.requires_eject").toBool()) {
221
QString devnode = m_device->property("block.device").toString();
223
#if defined(Q_OS_OPENBSD)
224
QString program = "cdio";
226
args << "-f" << devnode << "eject";
227
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
228
devnode.remove("/dev/").replace("([0-9]).", "\\1");
229
QString program = "cdcontrol";
231
args << "-f" << devnode << "eject";
233
QString program = "eject";
238
m_ejectInProgress = true;
239
m_device->broadcastActionRequested("eject");
240
m_process = FstabHandling::callSystemCommand("eject", args,
241
this, SLOT(slotProcessFinished(int, QProcess::ExitStatus)));
243
} else if (m_ejectInProgress) {
244
m_ejectInProgress = false;
245
m_device->broadcastActionDone("eject");
249
void StorageAccess::slotDBusError(const QDBusError &error)
251
// TODO: Better error reporting here
252
if (m_setupInProgress) {
253
if (error.name() == "org.freedesktop.Hal.Device.Volume.PermissionDenied") {
254
callHalPrivilegedVolumeMount();
257
m_setupInProgress = false;
258
m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation,
259
QString(error.name()+": "+error.message()));
260
} else if (m_teardownInProgress) {
261
if (error.name() == "org.freedesktop.Hal.Device.PermissionDeniedByPolicy") {
262
callHalPrivilegedVolumeUnmount();
266
m_teardownInProgress = false;
267
m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation,
268
QString(error.name()+": "+error.message()));
269
} else if (m_ejectInProgress) {
270
m_ejectInProgress = false;
271
m_device->broadcastActionDone("eject", Solid::UnauthorizedOperation,
272
QString(error.name()+": "+error.message()));
276
void Solid::Backends::Hal::StorageAccess::slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
278
Q_UNUSED(exitStatus);
279
if (m_setupInProgress) {
280
m_setupInProgress = false;
283
m_device->broadcastActionDone("setup");
285
m_device->broadcastActionDone("setup", Solid::UnauthorizedOperation,
286
m_process->readAllStandardError());
288
} else if (m_teardownInProgress) {
289
m_teardownInProgress = false;
291
m_device->broadcastActionDone("teardown");
293
m_device->broadcastActionDone("teardown", Solid::UnauthorizedOperation,
294
m_process->readAllStandardError());
296
} else if (m_ejectInProgress) {
298
m_ejectInProgress = false;
299
m_device->broadcastActionDone("eject");
301
callHalVolumeEject();
308
void StorageAccess::slotSetupRequested()
310
m_setupInProgress = true;
311
emit setupRequested(m_device->udi());
314
void StorageAccess::slotTeardownRequested()
316
m_teardownInProgress = true;
317
emit teardownRequested(m_device->udi());
320
void StorageAccess::slotEjectRequested()
322
m_ejectInProgress = true;
325
QString generateReturnObjectPath()
327
static int number = 1;
329
return "/org/kde/solid/HalStorageAccess_"+QString::number(number++);
332
bool StorageAccess::requestPassphrase()
334
QString udi = m_device->udi();
335
QString returnService = QDBusConnection::sessionBus().baseService();
336
m_lastReturnObject = generateReturnObjectPath();
338
QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this,
339
QDBusConnection::ExportScriptableSlots);
342
QWidget *activeWindow = QApplication::activeWindow();
344
if (activeWindow!=0) {
345
wId = (uint)activeWindow->winId();
348
QString appId = QCoreApplication::applicationName();
350
QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer");
351
QDBusReply<void> reply = soliduiserver.call("showPassphraseDialog", udi,
352
returnService, m_lastReturnObject,
354
m_passphraseRequested = reply.isValid();
355
if (!m_passphraseRequested) {
356
qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
358
return m_passphraseRequested;
361
void StorageAccess::passphraseReply(const QString &passphrase)
363
if (m_passphraseRequested) {
364
QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
365
m_passphraseRequested = false;
366
if (!passphrase.isEmpty()) {
367
callCryptoSetup(passphrase);
369
m_setupInProgress = false;
370
m_device->broadcastActionDone("setup");
375
bool StorageAccess::callHalVolumeMount()
377
QDBusConnection c = QDBusConnection::systemBus();
378
QString udi = m_device->udi();
379
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi,
380
"org.freedesktop.Hal.Device.Volume",
383
// HAL 0.5.12 supports using alternative drivers for the same filesystem.
384
// This is mainly used to integrate the ntfs-3g driver.
385
// Unfortunately, the primary driver gets used unless we
386
// specify some other driver (fstype) to the Mount method.
387
// TODO: Allow the user to choose the driver.
389
QString fstype = m_device->property("volume.fstype").toString();
390
QStringList halOptions = m_device->property("volume.mount.valid_options").toStringList();
392
QString alternativePreferred = m_device->property("volume.fstype.alternative.preferred").toString();
393
if (!alternativePreferred.isEmpty()) {
394
QStringList alternativeFstypes = m_device->property("volume.fstype.alternative").toStringList();
395
if (alternativeFstypes.contains(alternativePreferred)) {
396
fstype = alternativePreferred;
397
halOptions = m_device->property("volume.mount."+fstype+".valid_options").toStringList();
408
QString fsType = m_device->property("volume.fstype").toString();
410
if (halOptions.contains("uid=") && !fsType.contains("ntfs")) {
411
options << uid+QString::number(::getuid());
414
if (fsType.contains("ntfs")) {
415
options << "locale=" + QString(setlocale(LC_ALL, ""));
421
if ( fstype=="vfat" && halOptions.contains("-L=")) {
422
if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) )
423
options << "-L="+QString(cType);
425
else if ( (fstype.startsWith(QLatin1String("ntfs")) || fstype=="iso9660" || fstype=="udf") && halOptions.contains("-C=") ) {
426
if ((cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) )
427
options << "-C="+QString(nl_langinfo(CODESET));
430
if (fstype=="vfat" || fstype=="ntfs" || fstype=="iso9660" || fstype=="udf" ) {
431
if (halOptions.contains("utf8"))
433
else if (halOptions.contains("iocharset="))
434
options<<"iocharset=utf8";
435
if (halOptions.contains("shortname="))
436
options<<"shortname=mixed";
437
if (halOptions.contains("flush"))
440
// pass our locale to the ntfs-3g driver so it can translate local characters
441
else if ( halOptions.contains("locale=") ) {
442
// have to obtain LC_CTYPE as returned by the `locale` command
443
// check in the same order as `locale` does
445
if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) {
446
options << "locale="+QString(cType);
451
msg << "" << fstype << options;
453
return c.callWithCallback(msg, this,
454
SLOT(slotDBusReply(const QDBusMessage &)),
455
SLOT(slotDBusError(const QDBusError &)));
458
bool StorageAccess::callHalVolumeUnmount()
460
QDBusConnection c = QDBusConnection::systemBus();
461
QString udi = m_device->udi();
462
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi,
463
"org.freedesktop.Hal.Device.Volume",
466
msg << QStringList();
468
return c.callWithCallback(msg, this,
469
SLOT(slotDBusReply(const QDBusMessage &)),
470
SLOT(slotDBusError(const QDBusError &)));
473
bool StorageAccess::callHalVolumeEject()
475
QString udi = m_device->udi();
476
QString interface = "org.freedesktop.Hal.Device.Volume";
478
QDBusConnection c = QDBusConnection::systemBus();
479
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi,
482
msg << QStringList();
484
return c.callWithCallback(msg, this,
485
SLOT(slotDBusReply(const QDBusMessage &)),
486
SLOT(slotDBusError(const QDBusError &)));
489
bool Solid::Backends::Hal::StorageAccess::callSystemMount()
491
const QString device = m_device->property("block.device").toString();
492
m_process = FstabHandling::callSystemCommand("mount", device,
493
this, SLOT(slotProcessFinished(int, QProcess::ExitStatus)));
498
QString sudoCommandName()
500
return "/usr/lib/kde4/libexec/kdesu";
503
QString dbusSendCommandName()
508
QProcess* callPrivilegedCommand(const QString& command,
509
QObject* obj, const char* slot,
510
const QString& comment)
512
QProcess* process = new QProcess(obj);
514
QObject::connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
517
QStringList commandArgs;
518
commandArgs << "-d" << "--noignorebutton";
519
if(!comment.isEmpty())
520
commandArgs << "--comment" << comment;
521
commandArgs << "-c" << command;
523
process->start(sudoCommandName(), commandArgs);
525
if (process->waitForStarted()) {
533
bool Solid::Backends::Hal::StorageAccess::callHalPrivilegedVolumeMount()
536
QStringList halOptions = m_device->property("volume.mount.valid_options").toStringList();
538
QString fsType = m_device->property("volume.fstype").toString();
540
if (halOptions.contains("uid=") && !fsType.contains("ntfs")) {
541
options << "uid="+QString::number(::getuid());
544
if (fsType.contains("ntfs")) {
545
options << "locale=" + QString(setlocale(LC_ALL, ""));
549
QTextStream(&command) << dbusSendCommandName()
550
<< " --system --print-reply --dest=org.freedesktop.Hal " << m_device->udi()
551
<< " org.freedesktop.Hal.Device.Volume.Mount string:" << filePath()
552
<< " string: array:string:" << options.join(",");
554
m_process = callPrivilegedCommand(command,
556
SLOT(slotProcessFinished(int, QProcess::ExitStatus)),
557
QObject::tr("Please enter your password to use this device"));
559
return (m_process != 0);
562
bool Solid::Backends::Hal::StorageAccess::callHalPrivilegedVolumeUnmount()
565
QTextStream(&command) << dbusSendCommandName()
566
<< " --system --print-reply --dest=org.freedesktop.Hal " << m_device->udi()
567
<< " org.freedesktop.Hal.Device.Volume.Unmount array:string:force";
569
m_process = callPrivilegedCommand(command,
571
SLOT(slotProcessFinished(int, QProcess::ExitStatus)),
572
QObject::tr("Please enter your password to safely remove this device"));
574
return m_process != 0;
578
bool Solid::Backends::Hal::StorageAccess::callSystemUnmount()
580
const QString device = m_device->property("block.device").toString();
581
m_process = FstabHandling::callSystemCommand("umount", device,
582
this, SLOT(slotProcessFinished(int, QProcess::ExitStatus)));
587
void StorageAccess::callCryptoSetup(const QString &passphrase)
589
QDBusConnection c = QDBusConnection::systemBus();
590
QString udi = m_device->udi();
591
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi,
592
"org.freedesktop.Hal.Device.Volume.Crypto",
597
c.callWithCallback(msg, this,
598
SLOT(slotDBusReply(const QDBusMessage &)),
599
SLOT(slotDBusError(const QDBusError &)));
602
bool StorageAccess::callCryptoTeardown()
604
QDBusConnection c = QDBusConnection::systemBus();
605
QString udi = m_device->udi();
606
QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Hal", udi,
607
"org.freedesktop.Hal.Device.Volume.Crypto",
610
return c.callWithCallback(msg, this,
611
SLOT(slotDBusReply(const QDBusMessage &)),
612
SLOT(slotDBusError(const QDBusError &)));
615
#include "backends/hal/halstorageaccess.moc"