241 changes: 219 additions & 22 deletions mythtv/libs/libmyth/mediamonitor-unix.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// -*- Mode: c++ -*-
#include "config.h"

// Standard C headers
#include <cstdio>
Expand All @@ -22,6 +23,10 @@
using namespace std;

// Qt headers
#if CONFIG_QTDBUS
#include <QtDBus>
#include <QDBusConnection>
#endif
#include <QList>
#include <QTextStream>
#include <QDir>
Expand Down Expand Up @@ -66,6 +71,16 @@ extern "C" {
#endif
#define SUPER_OPT_DEV "dev="

#if CONFIG_QTDBUS
// DBus UDisk service - http://hal.freedesktop.org/docs/udisks/
#define UDISKS_SVC "org.freedesktop.UDisks"
#define UDISKS_PATH "/org/freedesktop/UDisks"
#define UDISKS_IFACE "org.freedesktop.UDisks"
#define UDISKS_DEVADD "DeviceAdded"
#define UDISKS_DEVRMV "DeviceRemoved"
#define UDISKS_DEVSIG "o" // OBJECT_PATH
#endif

const char * MediaMonitorUnix::kUDEV_FIFO = "/tmp/mythtv_media";


Expand All @@ -91,7 +106,7 @@ static void statError(const QString &methodName, const QString devPath)

MediaMonitorUnix::MediaMonitorUnix(QObject* par,
unsigned long interval, bool allowEject)
: MediaMonitor(par, interval, allowEject)
: MediaMonitor(par, interval, allowEject), m_fifo(-1)
{
CheckFileSystemTable();
CheckMountable();
Expand All @@ -100,6 +115,7 @@ MediaMonitorUnix::MediaMonitorUnix(QObject* par,
}


#if !CONFIG_QTDBUS
void MediaMonitorUnix::deleteLater(void)
{
if (m_fifo >= 0)
Expand All @@ -110,6 +126,7 @@ void MediaMonitorUnix::deleteLater(void)
}
MediaMonitor::deleteLater();
}
#endif // !CONFIG_QTDBUS


// Loop through the file system table and add any supported devices.
Expand All @@ -136,6 +153,21 @@ bool MediaMonitorUnix::CheckFileSystemTable(void)
return true;
}

#if CONFIG_QTDBUS
// Get a device property by name
static QVariant DeviceProperty(const QDBusObjectPath& o, const char kszProperty[])
{
QVariant v;

QDBusInterface iface(UDISKS_SVC, o.path(), UDISKS_IFACE".Device",
QDBusConnection::systemBus() );
if (iface.isValid())
v = iface.property(kszProperty);

return v;
}
#endif

/**
* \brief Search /sys/block for valid removable media devices.
*
Expand All @@ -149,7 +181,63 @@ bool MediaMonitorUnix::CheckFileSystemTable(void)
*/
bool MediaMonitorUnix::CheckMountable(void)
{
#ifdef linux
#if CONFIG_QTDBUS
// Listen on DBus for UDisk add/remove device messages
QDBusConnection::systemBus().connect(
UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVADD, UDISKS_DEVSIG,
this, SLOT(deviceAdded(QDBusObjectPath)) );
QDBusConnection::systemBus().connect(
UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVRMV, UDISKS_DEVSIG,
this, SLOT(deviceRemoved(QDBusObjectPath)) );

// Connect to UDisks
QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
QDBusConnection::systemBus() );
if (!iface.isValid())
{
VERBOSE(VB_IMPORTANT, LOC + ":CheckMountable: DBus interface error: " +
iface.lastError().message() );
return false;
}

// Enumerate devices
typedef QList<QDBusObjectPath> QDBusObjectPathList;
QDBusReply<QDBusObjectPathList> reply = iface.call("EnumerateDevices");
if (!reply.isValid())
{
VERBOSE(VB_IMPORTANT, LOC + ":CheckMountable DBus EnumerateDevices error: " +
reply.error().message() );
return false;
}

// Parse the returned device array
const QDBusObjectPathList& list(reply.value());
for (QDBusObjectPathList::const_iterator it = list.begin();
it != list.end(); ++it)
{
if (!DeviceProperty(*it, "DeviceIsSystemInternal").toBool() &&
!DeviceProperty(*it, "DeviceIsPartitionTable").toBool() )
{
QString dev = DeviceProperty(*it, "DeviceFile").toString();

// ignore floppies, too slow
if (dev.startsWith("/dev/fd"))
continue;

MythMediaDevice* pDevice;
if (DeviceProperty(*it, "DeviceIsRemovable").toBool())
pDevice = MythCDROM::get(this, dev.toAscii(), false, m_AllowEject);
else
pDevice = MythHDD::Get(this, dev.toAscii(), false, false);

if (pDevice && !AddDevice(pDevice))
pDevice->deleteLater();
}
}
return true;

#elif defined linux
// NB needs script in /etc/udev/rules.d
mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK);

Expand All @@ -174,11 +262,12 @@ bool MediaMonitorUnix::CheckMountable(void)
sysfs.cdUp();
}
return true;
#else // if !linux
#else // linux
return false;
#endif // !linux
#endif
}

#if !CONFIG_QTDBUS
/**
* \brief Is /sys/block/dev a removable device?
*/
Expand Down Expand Up @@ -227,7 +316,7 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
#ifdef linux
#if HAVE_LIBUDEV
// Use libudev to determine the name
ret = "";
ret.clear();
struct udev *udev = udev_new();
if (udev != NULL)
{
Expand All @@ -239,11 +328,27 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)

if (name != NULL)
ret = tr(name);
else
{
// This can happen when udev sends an AddDevice for a block
// device with partitions. FindPartition locates a partition
// in sysfs but udev hasn't created the devnode for it yet.
// Udev will send another AddDevice for the partition later.
VERBOSE(VB_MEDIA+VB_EXTRA, msg + " devnode not (yet) known");
}

udev_device_unref(device);
}
else
{
VERBOSE(VB_IMPORTANT, msg + " udev_device_new_from_syspath returned NULL");
ret = "";
}

udev_unref(udev);
}
else
VERBOSE(VB_IMPORTANT, "MediaMonitorUnix::GetDeviceFile udev_new failed");
#else // HAVE_LIBUDEV
// Use udevadm info to determine the name
QStringList args;
Expand Down Expand Up @@ -271,12 +376,9 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
}

QTextStream ostream(udevinfo->ReadAll());
ret = ostream.readLine();
if( ret.startsWith("device not found in database") )
{
delete udevinfo;
return ret;
}
QString udevLine = ostream.readLine();
if (!udevLine.startsWith("device not found in database") )
ret = udevLine;

delete udevinfo;
#endif // HAVE_LIBUDEV
Expand All @@ -285,16 +387,43 @@ QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
VERBOSE(VB_MEDIA, msg + "->'" + ret + "'");
return ret;
}
#endif // !CONFIG_QTDBUS

/*
* \brief Reads the list devices known to be CD or DVD devices.
* \return list of CD and DVD device names.
*/
// pure virtual
QStringList MediaMonitorUnix::GetCDROMBlockDevices(void)
{
QStringList l;

#ifdef linux
#if CONFIG_QTDBUS
QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
QDBusConnection::systemBus() );
if (iface.isValid())
{
// Enumerate devices
typedef QList<QDBusObjectPath> QDBusObjectPathList;
QDBusReply<QDBusObjectPathList> reply = iface.call("EnumerateDevices");
if (reply.isValid())
{
const QDBusObjectPathList& list(reply.value());
for (QDBusObjectPathList::const_iterator it = list.begin();
it != list.end(); ++it)
{
if (DeviceProperty(*it, "DeviceIsRemovable").toBool())
{
QString dev = DeviceProperty(*it, "DeviceFile").toString();
if (dev.startsWith("/dev/"))
dev.remove(0,5);
l.push_back(dev);
}
}
}
}

#elif defined linux
QFile file("/proc/sys/dev/cdrom/info");
if (file.open(QIODevice::ReadOnly))
{
Expand All @@ -315,21 +444,36 @@ QStringList MediaMonitorUnix::GetCDROMBlockDevices(void)
}
#endif // linux

VERBOSE(VB_MEDIA, LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'");
VERBOSE(VB_MEDIA+VB_EXTRA,
LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'");
return l;
}

static void LookupModel(MythMediaDevice* device)
{
QString desc;
QString devname = device->getRealDevice();

#if CONFIG_QTDBUS
QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
QDBusConnection::systemBus() );
if (iface.isValid())
{
QDBusReply<QDBusObjectPath> reply = iface.call(
"FindDeviceByDeviceFile", device->getRealDevice());
if (reply.isValid())
{
desc = DeviceProperty(reply, "DriveVendor").toString();
if (!desc.isEmpty())
desc += " ";
desc += DeviceProperty(reply, "DriveModel").toString();
}
}

#elif defined linux

// Given something like /dev/hda1, extract hda1
devname = devname.mid(5,5);
QString devname = device->getRealDevice().mid(5,5);


#ifdef linux
if (devname.startsWith("hd")) // IDE drive
{
QFile file("/proc/ide/" + devname.left(3) + "/model");
Expand Down Expand Up @@ -373,6 +517,8 @@ static void LookupModel(MythMediaDevice* device)
}
#endif

VERBOSE(VB_MEDIA+VB_EXTRA, QString("LookupModel '%1' -> '%2'").
arg(device->getRealDevice()).arg(desc) );
device->setDeviceModel(desc.toAscii().constData());
}

Expand Down Expand Up @@ -534,6 +680,49 @@ bool MediaMonitorUnix::AddDevice(struct fstab * mep)
return false;
}

#if CONFIG_QTDBUS
/*
* DBus UDisk AddDevice handler
*/
void MediaMonitorUnix::deviceAdded( QDBusObjectPath o)
{
VERBOSE(VB_MEDIA, LOC + ":deviceAdded " + o.path());

// Don't add devices with partition tables, just the partitions
if (!DeviceProperty(o, "DeviceIsPartitionTable").toBool())
{
QString dev = DeviceProperty(o, "DeviceFile").toString();

MythMediaDevice* pDevice;
if (DeviceProperty(o, "DeviceIsRemovable").toBool())
pDevice = MythCDROM::get(this, dev.toAscii(), false, m_AllowEject);
else
pDevice = MythHDD::Get(this, dev.toAscii(), false, false);

if (pDevice && !AddDevice(pDevice))
pDevice->deleteLater();
}
}

/*
* DBus UDisk RemoveDevice handler
*/
void MediaMonitorUnix::deviceRemoved( QDBusObjectPath o)
{
VERBOSE(VB_MEDIA, LOC + "deviceRemoved " + o.path());
#if 0 // This fails because the DeviceFile has just been deleted
QString dev = DeviceProperty(o, "DeviceFile");
if (!dev.isEmpty())
RemoveDevice(dev);
#else
QString dev = QFileInfo(o.path()).baseName();
dev.prepend("/dev/");
RemoveDevice(dev);
#endif
}

#else //CONFIG_QTDBUS

/**
* \brief Creates MythMedia instances for sysfs removable media devices.
*
Expand All @@ -552,6 +741,8 @@ bool MediaMonitorUnix::AddDevice(struct fstab * mep)
*/
bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
{
VERBOSE(VB_MEDIA+VB_EXTRA, LOC + ":FindPartitions(" + dev + QString(",%1").
arg(checkPartitions ? " true" : " false" ) + ")");
MythMediaDevice* pDevice = NULL;

if (checkPartitions)
Expand All @@ -570,7 +761,8 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)

// skip some sysfs dirs that are _not_ sub-partitions
if (*pit == "device" || *pit == "holders" || *pit == "queue"
|| *pit == "slaves" || *pit == "subsystem")
|| *pit == "slaves" || *pit == "subsystem"
|| *pit == "bdi" || *pit == "power")
continue;

found_partitions |= FindPartitions(
Expand All @@ -586,7 +778,7 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)

QString device_file = GetDeviceFile(dev);

if (device_file.isNull())
if (device_file.isEmpty())
return false;

QStringList cdroms = GetCDROMBlockDevices();
Expand Down Expand Up @@ -621,9 +813,9 @@ bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
void MediaMonitorUnix::CheckDeviceNotifications(void)
{
char buffer[256];
QString qBuffer = "";
QString qBuffer;

if (!m_fifo)
if (m_fifo == -1)
return;

int size = read(m_fifo, buffer, 255);
Expand All @@ -642,13 +834,18 @@ void MediaMonitorUnix::CheckDeviceNotifications(void)
if ((*it).startsWith("add"))
{
QString dev = (*it).section(' ', 1, 1);
VERBOSE(VB_MEDIA, "Udev add " + dev);

if (CheckRemovable(dev))
FindPartitions(dev, true);
}
else if ((*it).startsWith("remove"))
{
RemoveDevice((*it).section(' ', 2, 2));
QString dev = (*it).section(' ', 2, 2);
VERBOSE(VB_MEDIA, "Udev remove " + dev);
RemoveDevice(dev);
}
}
}
#endif //!CONFIG_QTDBUS

29 changes: 26 additions & 3 deletions mythtv/libs/libmyth/mediamonitor-unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,51 @@
#define DEFAULT_DVD "/dev/dvd"
#define DEFAULT_CD "/dev/cdrom"

#include "config.h"

#include <QString>
#if CONFIG_QTDBUS
#include <QtDBus>
#endif

#include "mythmediamonitor.h"

class MediaMonitorUnix : public MediaMonitor
{
#if CONFIG_QTDBUS
Q_OBJECT
public slots:
Q_NOREPLY void deviceAdded(QDBusObjectPath);
Q_NOREPLY void deviceRemoved(QDBusObjectPath);
#endif

public:
MediaMonitorUnix(QObject *par, unsigned long interval, bool allowEject);
#if !CONFIG_QTDBUS
virtual void deleteLater(void);
#endif

protected:
~MediaMonitorUnix() {}

void CheckDevices(void);
void CheckDeviceNotifications(void);
#if !CONFIG_QTDBUS
virtual void CheckDeviceNotifications(void);
#endif
bool CheckFileSystemTable(void);
bool CheckMountable(void);
#if !CONFIG_QTDBUS
bool CheckRemovable(const QString &dev);
bool FindPartitions(const QString &dev, bool checkPartitions);
#endif

virtual bool AddDevice(MythMediaDevice* pDevice);
bool AddDevice(struct fstab* mep);

#if !CONFIG_QTDBUS
QString GetDeviceFile(const QString &sysfs);
#endif

QStringList GetCDROMBlockDevices(void);
virtual QStringList GetCDROMBlockDevices(void);

protected:
int m_fifo;
Expand Down