1 /*
2     Copyright 2009 Pino Toscano <pino@kde.org>
3     Copyright 2009, 2011 Lukas Tinkl <ltinkl@redhat.com>
4 
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Lesser General Public
7     License as published by the Free Software Foundation; either
8     version 2.1 of the License, or (at your option) version 3, or any
9     later version accepted by the membership of KDE e.V. (or its
10     successor approved by the membership of KDE e.V.), which shall
11     act as a proxy defined in Section 6 of version 3 of the license.
12 
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Lesser General Public License for more details.
17 
18     You should have received a copy of the GNU Lesser General Public
19     License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include "udisksstorageaccess.h"
23 #include "udisks.h"
24 
25 #include <QProcess>
26 #include <QtDBus>
27 #include <QApplication>
28 #include <QWidget>
29 
30 using namespace Solid::Backends::UDisks;
31 
UDisksStorageAccess(UDisksDevice * device)32 UDisksStorageAccess::UDisksStorageAccess(UDisksDevice *device)
33     : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false)
34 {
35     connect(device, SIGNAL(changed()), this, SLOT(slotChanged()));
36     updateCache();
37 
38     // Delay connecting to DBus signals to avoid the related time penalty
39     // in hot paths such as predicate matching
40     QTimer::singleShot(0, this, SLOT(connectDBusSignals()));
41 }
42 
~UDisksStorageAccess()43 UDisksStorageAccess::~UDisksStorageAccess()
44 {
45 }
46 
connectDBusSignals()47 void UDisksStorageAccess::connectDBusSignals()
48 {
49     m_device->registerAction("setup", this,
50                              SLOT(slotSetupRequested()),
51                              SLOT(slotSetupDone(int,QString)));
52 
53     m_device->registerAction("teardown", this,
54                              SLOT(slotTeardownRequested()),
55                              SLOT(slotTeardownDone(int,QString)));
56 }
57 
isLuksDevice() const58 bool UDisksStorageAccess::isLuksDevice() const
59 {
60     return m_device->prop("DeviceIsLuks").toBool();
61 }
62 
isAccessible() const63 bool UDisksStorageAccess::isAccessible() const
64 {
65     if (isLuksDevice()) { // check if the cleartext slave is mounted
66         UDisksDevice holderDevice(m_device->prop("LuksHolder").value<QDBusObjectPath>().path());
67         return holderDevice.prop("DeviceIsMounted").toBool();
68     }
69 
70     return m_device->prop("DeviceIsMounted").toBool();
71 }
72 
filePath() const73 QString UDisksStorageAccess::filePath() const
74 {
75     if (!isAccessible())
76         return QString();
77 
78     QStringList mntPoints;
79 
80     if (isLuksDevice()) {  // encrypted (and unlocked) device
81         QString path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
82         if (path.isEmpty() || path == "/")
83             return QString();
84         UDisksDevice holderDevice(path);
85         mntPoints = holderDevice.prop("DeviceMountPaths").toStringList();
86         if (!mntPoints.isEmpty())
87             return mntPoints.first(); // FIXME Solid doesn't support multiple mount points
88         else
89             return QString();
90     }
91 
92     mntPoints = m_device->prop("DeviceMountPaths").toStringList();
93 
94     if (!mntPoints.isEmpty())
95         return mntPoints.first(); // FIXME Solid doesn't support multiple mount points
96     else
97         return QString();
98 }
99 
isIgnored() const100 bool UDisksStorageAccess::isIgnored() const
101 {
102     return m_device->isDeviceBlacklisted();
103 }
104 
setup()105 bool UDisksStorageAccess::setup()
106 {
107     if ( m_teardownInProgress || m_setupInProgress )
108         return false;
109     m_setupInProgress = true;
110     m_device->broadcastActionRequested("setup");
111 
112     if (m_device->prop("IdUsage").toString() == "crypto")
113         return requestPassphrase();
114     else
115         return mount();
116 }
117 
teardown()118 bool UDisksStorageAccess::teardown()
119 {
120     if ( m_teardownInProgress || m_setupInProgress )
121         return false;
122     m_teardownInProgress = true;
123     m_device->broadcastActionRequested("teardown");
124 
125     return unmount();
126 }
127 
slotChanged()128 void UDisksStorageAccess::slotChanged()
129 {
130     const bool old_isAccessible = m_isAccessible;
131     updateCache();
132 
133     if (old_isAccessible != m_isAccessible)
134     {
135         emit accessibilityChanged(m_isAccessible, m_device->udi());
136     }
137 }
138 
updateCache()139 void UDisksStorageAccess::updateCache()
140 {
141     m_isAccessible = isAccessible();
142 }
143 
slotDBusReply(const QDBusMessage & reply)144 void UDisksStorageAccess::slotDBusReply( const QDBusMessage & reply )
145 {
146     Q_UNUSED(reply);
147     if (m_setupInProgress)
148     {
149         if (isLuksDevice() && !isAccessible())  // unlocked device, now mount it
150             mount();
151 
152         else // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156)
153         {
154             m_setupInProgress = false;
155             m_device->broadcastActionDone("setup");
156         }
157     }
158     else if (m_teardownInProgress)
159     {
160         QString clearTextPath =  m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
161         if (isLuksDevice() && clearTextPath != "/") // unlocked device, lock it
162         {
163             callCryptoTeardown();
164         }
165         else if (m_device->prop("DeviceIsLuksCleartext").toBool()) {
166             callCryptoTeardown(true); // Lock crypted parent
167         }
168         else
169         {
170             if (m_device->prop("DriveIsMediaEjectable").toBool() &&
171                     m_device->prop("DeviceIsMediaAvailable").toBool() &&
172                     !m_device->prop("DeviceIsOpticalDisc").toBool()) // optical drives have their Eject method
173             {
174                 QString devnode = m_device->prop("DeviceFile").toString();
175 
176 #if defined(Q_OS_OPENBSD)
177                 QString program = "cdio";
178                 QStringList args;
179                 args << "-f" << devnode << "eject";
180 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
181                 devnode.remove("/dev/").replace("([0-9]).", "\\1");
182                 QString program = "cdcontrol";
183                 QStringList args;
184                 args << "-f" << devnode << "eject";
185 #else
186                 QString program = "eject";
187                 QStringList args;
188                 args << devnode;
189 #endif
190 
191                 QProcess::startDetached( program, args );
192             }
193 
194             // try to eject the (parent) drive, e.g. SD card from a reader
195             QString drivePath = m_device->prop("PartitionSlave").value<QDBusObjectPath>().path();
196             if (!drivePath.isEmpty() || drivePath != "/")
197             {
198                 QDBusConnection c = QDBusConnection::systemBus();
199                 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveEject");
200                 msg << QStringList();   // options, unused now
201                 c.call(msg, QDBus::NoBlock);
202 
203                 // power down removable USB hard drives, rhbz#852196
204                 UDisksDevice drive(drivePath);
205                 if (drive.prop("DriveCanDetach").toBool()) {
206                     QDBusMessage msg2 = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveDetach");
207                     msg2 << QStringList();   // options, unused now
208                     c.call(msg2, QDBus::NoBlock);
209                 }
210             }
211 
212             m_teardownInProgress = false;
213             m_device->broadcastActionDone("teardown");
214         }
215     }
216 }
217 
slotDBusError(const QDBusError & error)218 void UDisksStorageAccess::slotDBusError( const QDBusError & error )
219 {
220     if (m_setupInProgress)
221     {
222         m_setupInProgress = false;
223         m_device->broadcastActionDone("setup", m_device->errorToSolidError(error.name()),
224                                       m_device->errorToString(error.name()) + ": " +error.message());
225 
226     }
227     else if (m_teardownInProgress)
228     {
229         m_teardownInProgress = false;
230         m_device->broadcastActionDone("teardown", m_device->errorToSolidError(error.name()),
231                                       m_device->errorToString(error.name()) + ": " + error.message());
232     }
233 }
234 
slotSetupRequested()235 void UDisksStorageAccess::slotSetupRequested()
236 {
237     m_setupInProgress = true;
238     emit setupRequested(m_device->udi());
239 }
240 
slotSetupDone(int error,const QString & errorString)241 void UDisksStorageAccess::slotSetupDone(int error, const QString &errorString)
242 {
243     m_setupInProgress = false;
244     emit setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
245     slotChanged();
246 }
247 
slotTeardownRequested()248 void UDisksStorageAccess::slotTeardownRequested()
249 {
250     m_teardownInProgress = true;
251     emit teardownRequested(m_device->udi());
252 }
253 
slotTeardownDone(int error,const QString & errorString)254 void UDisksStorageAccess::slotTeardownDone(int error, const QString &errorString)
255 {
256     m_teardownInProgress = false;
257     emit teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
258     slotChanged();
259 }
260 
mount()261 bool UDisksStorageAccess::mount()
262 {
263     QString path = m_device->udi();
264     if (path.endsWith(":media")) {
265         path.chop(6);
266     }
267     QString fstype;
268     QStringList options;
269 
270     if (isLuksDevice()) { // mount options for the cleartext volume
271         path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
272         UDisksDevice holderDevice(path);
273         fstype = holderDevice.prop("IdType").toString();
274     }
275 
276     QDBusConnection c = QDBusConnection::systemBus();
277     QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemMount");
278 
279     if (m_device->prop("IdUsage").toString() == "filesystem")
280         fstype = m_device->prop("IdType").toString();
281 
282     if (fstype == "vfat") {
283         options << "flush";
284     }
285 
286     msg << fstype;
287     msg << options;
288 
289     return c.callWithCallback(msg, this,
290                               SLOT(slotDBusReply(QDBusMessage)),
291                               SLOT(slotDBusError(QDBusError)));
292 }
293 
unmount()294 bool UDisksStorageAccess::unmount()
295 {
296     QString path = m_device->udi();
297     if (path.endsWith(":media")) {
298         path.chop(6);
299     }
300 
301     if (isLuksDevice()) { // unmount options for the cleartext volume
302         path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
303     }
304 
305     QDBusConnection c = QDBusConnection::systemBus();
306     QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemUnmount");
307 
308     msg << QStringList();   // options, unused now
309 
310     return c.callWithCallback(msg, this,
311                               SLOT(slotDBusReply(QDBusMessage)),
312                               SLOT(slotDBusError(QDBusError)),
313                               s_unmountTimeout);
314 }
315 
generateReturnObjectPath()316 QString UDisksStorageAccess::generateReturnObjectPath()
317 {
318     static int number = 1;
319 
320     return "/org/kde/solid/UDisksStorageAccess_"+QString::number(number++);
321 }
322 
requestPassphrase()323 bool UDisksStorageAccess::requestPassphrase()
324 {
325     QString udi = m_device->udi();
326     QString returnService = QDBusConnection::sessionBus().baseService();
327     m_lastReturnObject = generateReturnObjectPath();
328 
329     QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots);
330 
331     QWidget *activeWindow = QApplication::activeWindow();
332     uint wId = 0;
333     if (activeWindow!=0)
334         wId = (uint)activeWindow->winId();
335 
336     QString appId = QCoreApplication::applicationName();
337 
338     QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer");
339     QDBusReply<void> reply = soliduiserver.call("showPassphraseDialog", udi, returnService,
340                                                 m_lastReturnObject, wId, appId);
341     m_passphraseRequested = reply.isValid();
342     if (!m_passphraseRequested)
343         qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
344 
345     return m_passphraseRequested;
346 }
347 
passphraseReply(const QString & passphrase)348 void UDisksStorageAccess::passphraseReply( const QString & passphrase )
349 {
350     if (m_passphraseRequested)
351     {
352         QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
353         m_passphraseRequested = false;
354         if (!passphrase.isEmpty())
355             callCryptoSetup(passphrase);
356         else
357         {
358             m_setupInProgress = false;
359             m_device->broadcastActionDone("setup");
360         }
361     }
362 }
363 
callCryptoSetup(const QString & passphrase)364 void UDisksStorageAccess::callCryptoSetup( const QString & passphrase )
365 {
366     QDBusConnection c = QDBusConnection::systemBus();
367     QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, m_device->udi(), UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksUnlock");
368 
369     msg << passphrase;
370     msg << QStringList();   // options, unused now
371 
372     c.callWithCallback(msg, this,
373                        SLOT(slotDBusReply(QDBusMessage)),
374                        SLOT(slotDBusError(QDBusError)));
375 }
376 
callCryptoTeardown(bool actOnParent)377 bool UDisksStorageAccess::callCryptoTeardown(bool actOnParent)
378 {
379     QDBusConnection c = QDBusConnection::systemBus();
380     QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE,
381                         actOnParent?(m_device->prop("LuksCleartextSlave").value<QDBusObjectPath>().path()):m_device->udi(),
382                         UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksLock");
383     msg << QStringList();   // options, unused now
384 
385     return c.callWithCallback(msg, this,
386                               SLOT(slotDBusReply(QDBusMessage)),
387                               SLOT(slotDBusError(QDBusError)));
388 }
389 
390 #include "backends/udisks/moc_udisksstorageaccess.cpp"
391