00001
00002
00003
00004 #include <cstdio>
00005
00006
00007 #include <dirent.h>
00008 #include <unistd.h>
00009 #include <fcntl.h>
00010 #include <fstab.h>
00011
00012
00013 #include <sys/file.h>
00014 #include <sys/types.h>
00015 #include <sys/stat.h>
00016 #include <sys/wait.h>
00017 #include <sys/param.h>
00018
00019
00020 #include <iostream>
00021 using namespace std;
00022
00023
00024 #include <qapplication.h>
00025 #include <qprocess.h>
00026 #include <qdir.h>
00027 #include <qfile.h>
00028
00029
00030 #include "mythmediamonitor.h"
00031 #include "mediamonitor-unix.h"
00032 #include "mythcontext.h"
00033 #include "mythdialogs.h"
00034 #include "mythconfig.h"
00035 #include "mythcdrom.h"
00036 #include "mythhdd.h"
00037
00038 #ifndef MNTTYPE_ISO9660
00039 #ifdef linux
00040 #define MNTTYPE_ISO9660 "iso9660"
00041 #elif defined(__FreeBSD__) || defined(CONFIG_DARWIN) || defined(__OpenBSD__)
00042 #define MNTTYPE_ISO9660 "cd9660"
00043 #endif
00044 #endif
00045
00046 #ifndef MNTTYPE_UDF
00047 #define MNTTYPE_UDF "udf"
00048 #endif
00049
00050 #ifndef MNTTYPE_AUTO
00051 #define MNTTYPE_AUTO "auto"
00052 #endif
00053
00054 #ifndef MNTTYPE_SUPERMOUNT
00055 #define MNTTYPE_SUPERMOUNT "supermount"
00056 #endif
00057 #define SUPER_OPT_DEV "dev="
00058
00059 const QString MediaMonitorUnix::kUDEV_FIFO = "/tmp/mythtv_media";
00060
00061
00062
00063
00064 static const QString LOC = QString("MediaMonitorUnix:");
00065
00066 static void fstabError(const QString &methodName)
00067 {
00068 VERBOSE(VB_IMPORTANT, LOC + methodName + " Error: failed to open "
00069 + _PATH_FSTAB + " for reading, " + ENO);
00070 }
00071
00072 static void statError(const QString &methodName, const QString devPath)
00073 {
00074 VERBOSE(VB_IMPORTANT, LOC + methodName + " Error: failed to stat "
00075 + devPath + ", " + ENO);
00076 }
00077
00079
00080
00081
00082 MediaMonitorUnix::MediaMonitorUnix(QObject* par,
00083 unsigned long interval, bool allowEject)
00084 : MediaMonitor(par, interval, allowEject)
00085 {
00086 CheckFileSystemTable();
00087 CheckMountable();
00088
00089 VERBOSE(VB_MEDIA, "Initial device list...\n" + listDevices());
00090 }
00091
00092
00093 MediaMonitorUnix::~MediaMonitorUnix()
00094 {
00095 if (m_fifo > 0)
00096 {
00097 close(m_fifo);
00098 unlink(kUDEV_FIFO);
00099 }
00100 }
00101
00102
00103
00104 bool MediaMonitorUnix::CheckFileSystemTable(void)
00105 {
00106 struct fstab * mep = NULL;
00107
00108
00109 if (!setfsent())
00110 {
00111 fstabError(":CheckFileSystemTable()");
00112 return false;
00113 }
00114
00115
00116 while ((mep = getfsent()) != NULL)
00117 AddDevice(mep);
00118
00119 endfsent();
00120
00121 if (m_Devices.isEmpty())
00122 return false;
00123
00124 return true;
00125 }
00126
00138 bool MediaMonitorUnix::CheckMountable(void)
00139 {
00140 #ifdef linux
00141 mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
00142 m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK);
00143
00144 QDir sysfs("/sys/block");
00145 sysfs.setFilter(QDir::Dirs);
00146
00147 QStringList devices = sysfs.entryList();
00148
00149 for (QStringList::iterator it = devices.begin(); it != devices.end(); ++it)
00150 {
00151 if (*it == "." || *it == "..")
00152 continue;
00153
00154
00155 if ((*it).startsWith("fd"))
00156 continue;
00157
00158 sysfs.cd(*it);
00159
00160 QFile removable(sysfs.absFilePath("removable"));
00161 if (removable.exists())
00162 {
00163 removable.open(IO_ReadOnly);
00164 int c = removable.getch();
00165 removable.close();
00166
00167 if (c == '1')
00168 FindPartitions(sysfs.absPath(), true);
00169 }
00170 sysfs.cdUp();
00171 }
00172 return true;
00173 #else // if !linux
00174 return false;
00175 #endif // !linux
00176 }
00177
00183 QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
00184 {
00185 #ifdef linux
00186 QProcess udevinfo(this);
00187 udevinfo.addArgument("udevinfo");
00188 udevinfo.addArgument("-q");
00189 udevinfo.addArgument("name");
00190 udevinfo.addArgument("-rp");
00191 udevinfo.addArgument(sysfs);
00192 udevinfo.setCommunication(QProcess::Stdout|QProcess::Stderr);
00193
00194 udevinfo.start();
00195
00196 int status;
00197 waitpid(udevinfo.processIdentifier(), &status, 0);
00198
00199 if (udevinfo.canReadLineStderr())
00200 VERBOSE(VB_MEDIA, LOC + QString(":GetDeviceFile(%1) - ").arg(sysfs)
00201 + udevinfo.readLineStderr());
00202
00203 QString ret = udevinfo.readLineStdout();
00204 if (ret != "device not found in database")
00205 return ret;
00206
00207 #endif // linux
00208 (void)sysfs;
00209 return QString::null;
00210 }
00211
00212
00213
00214
00215
00216 QStringList MediaMonitorUnix::GetCDROMBlockDevices(void)
00217 {
00218 QStringList l;
00219
00220 #ifdef linux
00221 QFile file("/proc/sys/dev/cdrom/info");
00222 if (file.open(IO_ReadOnly))
00223 {
00224 QTextStream stream(&file);
00225 QString line;
00226 while (!stream.atEnd())
00227 {
00228 line = stream.readLine();
00229 if (line.startsWith("drive name:"))
00230 {
00231 QStringList devs = QStringList::split('\t', line);
00232
00233 devs.pop_front();
00234 l += devs;
00235 }
00236 }
00237 file.close();
00238 }
00239 #endif // linux
00240
00241 VERBOSE(VB_MEDIA, "MediaMonitorUnix::GetCDROMBlockDevices() returning "
00242 + l.join(", "));
00243 return l;
00244 }
00245
00246 static void LookupModel(MythMediaDevice* device)
00247 {
00248 QString desc;
00249 QString devname = device->getRealDevice();
00250
00251
00252
00253 devname = devname.mid(5,5);
00254
00255
00256 #ifdef linux
00257 if (devname.startsWith("hd"))
00258 {
00259 QFile file("/proc/ide/" + devname.left(3) + "/model");
00260 if (file.open(IO_ReadOnly))
00261 {
00262 QTextStream stream(&file);
00263
00264 desc.append(stream.readLine());
00265 file.close();
00266 }
00267 }
00268
00269 if (devname.startsWith("scd"))
00270 devname.replace("scd", "sr");
00271
00272 if (devname.startsWith("sd")
00273 || devname.startsWith("sr"))
00274 {
00275 QString path = devname.prepend("/sys/block/");
00276 path.append("/device/");
00277
00278 QFile file(path + "vendor");
00279 if (file.open(IO_ReadOnly))
00280 {
00281 QTextStream stream(&file);
00282
00283 desc.append(stream.readLine());
00284 desc.append(' ');
00285 file.close();
00286 }
00287
00288 file.setName(path + "model");
00289 if (file.open(IO_ReadOnly))
00290 {
00291 QTextStream stream(&file);
00292
00293 desc.append(stream.readLine());
00294 desc.append(' ');
00295 file.close();
00296 }
00297 }
00298 #endif
00299
00300 device->setDeviceModel(desc);
00301 }
00302
00306 bool MediaMonitorUnix::AddDevice(MythMediaDevice* pDevice)
00307 {
00308 if ( ! pDevice )
00309 {
00310 VERBOSE(VB_IMPORTANT, "Error - MediaMonitorUnix::AddDevice(null)");
00311 return false;
00312 }
00313
00314
00315 if (shouldIgnore(pDevice))
00316 return false;
00317
00318 QString path = pDevice->getDevicePath();
00319 if (!path.length())
00320 {
00321 VERBOSE(VB_IMPORTANT,
00322 "MediaMonitorUnix::AddDevice() - empty device path.");
00323 return false;
00324 }
00325
00326 dev_t new_rdev;
00327 struct stat sb;
00328
00329 if (stat(path, &sb) < 0)
00330 {
00331 statError(":AddDevice()", path);
00332 return false;
00333 }
00334 new_rdev = sb.st_rdev;
00335
00336
00337
00338
00339 QValueList<MythMediaDevice*>::const_iterator itr = m_Devices.begin();
00340 for (; itr != m_Devices.end(); ++itr)
00341 {
00342 if (stat((*itr)->getDevicePath(), &sb) < 0)
00343 {
00344 statError(":AddDevice()", (*itr)->getDevicePath());
00345 return false;
00346 }
00347
00348 if (sb.st_rdev == new_rdev)
00349 {
00350 VERBOSE(VB_MEDIA, LOC + ":AddDevice() - not adding " + path
00351 + "\n "
00352 "because it appears to be a duplicate of "
00353 + (*itr)->getDevicePath());
00354 return false;
00355 }
00356 }
00357
00358 LookupModel(pDevice);
00359
00360 QMutexLocker locker(&m_DevicesLock);
00361
00362 connect(pDevice, SIGNAL(statusChanged(MediaStatus, MythMediaDevice*)),
00363 this, SLOT(mediaStatusChanged(MediaStatus, MythMediaDevice*)));
00364 m_Devices.push_back( pDevice );
00365 m_UseCount[pDevice] = 0;
00366 VERBOSE(VB_MEDIA, LOC + ":AddDevice() - Added " + path);
00367
00368 return true;
00369 }
00370
00371
00372 bool MediaMonitorUnix::AddDevice(struct fstab * mep)
00373 {
00374 if (!mep)
00375 return false;
00376
00377 QString devicePath( mep->fs_spec );
00378
00379
00380 MythMediaDevice* pDevice = NULL;
00381 struct stat sbuf;
00382
00383 bool is_supermount = false;
00384 bool is_cdrom = false;
00385
00386 if (stat(mep->fs_spec, &sbuf) < 0)
00387 return false;
00388
00389
00390 if ( ! ( ((strstr(mep->fs_mntops, "owner") &&
00391 (sbuf.st_mode & S_IRUSR)) || strstr(mep->fs_mntops, "user")) &&
00392 (strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
00393 strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
00394 strstr(mep->fs_vfstype, MNTTYPE_AUTO)) ) )
00395 {
00396 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
00397 strstr(mep->fs_vfstype, MNTTYPE_SUPERMOUNT))
00398 {
00399 is_supermount = true;
00400 }
00401 else
00402 {
00403 return false;
00404 }
00405 }
00406
00407 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
00408 strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
00409 strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
00410 strstr(mep->fs_vfstype, MNTTYPE_AUTO))
00411 {
00412 is_cdrom = true;
00413
00414 }
00415
00416 if (!is_supermount)
00417 {
00418 if (is_cdrom)
00419 pDevice = MythCDROM::get(this, QString(mep->fs_spec),
00420 is_supermount, m_AllowEject);
00421 }
00422 else
00423 {
00424 char *dev;
00425 int len = 0;
00426 dev = strstr(mep->fs_mntops, SUPER_OPT_DEV);
00427 dev += sizeof(SUPER_OPT_DEV)-1;
00428 while (dev[len] != ',' && dev[len] != ' ' && dev[len] != 0)
00429 len++;
00430
00431 if (dev[len] != 0)
00432 {
00433 char devstr[256];
00434 strncpy(devstr, dev, len);
00435 devstr[len] = 0;
00436 if (is_cdrom)
00437 pDevice = MythCDROM::get(this, QString(devstr),
00438 is_supermount, m_AllowEject);
00439 }
00440 else
00441 return false;
00442 }
00443
00444 if (pDevice)
00445 {
00446 pDevice->setMountPath(mep->fs_file);
00447 if (pDevice->testMedia() == MEDIAERR_OK)
00448 {
00449 if (AddDevice(pDevice))
00450 return true;
00451 }
00452 pDevice->deleteLater();
00453 }
00454
00455 return false;
00456 }
00457
00458
00459
00460 bool MediaMonitorUnix::AddDevice(const char* devPath)
00461 {
00462 QString devicePath( devPath );
00463
00464
00465 struct fstab * mep = NULL;
00466 char lpath[PATH_MAX];
00467
00468
00469 int len = readlink(devicePath, lpath, PATH_MAX);
00470 if (len > 0 && len < PATH_MAX)
00471 lpath[len] = 0;
00472
00473
00474 if (!setfsent())
00475 {
00476 fstabError(QString(":AddDevice(%2)").arg(devPath));
00477 return false;
00478 }
00479
00480
00481 while ((mep = getfsent()) != NULL)
00482 {
00483 #if 0
00484 cout << "***************************************************" << endl;
00485 cout << "devicePath == " << devicePath << endl;
00486 cout << "mep->fs_spec == " << mep->fs_spec << endl;
00487 cout << "lpath == " << lpath << endl;
00488 cout << "strcmp(devicePath, mep->fs_spec) == "
00489 << strcmp(devicePath, mep->fs_spec) << endl;
00490 cout << "len ==" << len << endl;
00491 cout << "strcmp(lpath, mep->fs_spec)=="
00492 << strcmp(lpath, mep->fs_spec) << endl;
00493 cout <<endl << endl;
00494 #endif
00495
00496
00497 if ((strcmp(devicePath, mep->fs_spec) != 0) &&
00498 (len && (strcmp(lpath, mep->fs_spec) != 0)))
00499 continue;
00500 }
00501
00502 endfsent();
00503
00504 if (mep)
00505 return AddDevice(mep);
00506
00507 return false;
00508 }
00509
00526 bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
00527 {
00528 MythMediaDevice* pDevice = NULL;
00529
00530 if (checkPartitions)
00531 {
00532
00533 QDir sysfs(dev);
00534 sysfs.setFilter(QDir::Dirs);
00535
00536 bool found_partitions = false;
00537 QStringList parts = sysfs.entryList();
00538 for (QStringList::iterator pit = parts.begin();
00539 pit != parts.end(); pit++)
00540 {
00541 if (*pit == "." || *pit == "..")
00542 continue;
00543
00544
00545 if (*pit == "device" || *pit == "holders" || *pit == "queue"
00546 || *pit == "slaves" || *pit == "subsystem")
00547 continue;
00548
00549 found_partitions |= FindPartitions(sysfs.absFilePath(*pit), false);
00550 }
00551
00552
00553 if (!found_partitions)
00554 found_partitions |= FindPartitions(sysfs.absPath(), false);
00555
00556 return found_partitions;
00557 }
00558
00559 QStringList cdroms = GetCDROMBlockDevices();
00560
00561 if (cdroms.contains(dev.section('/', -1)))
00562 {
00563
00564 QString device_file = GetDeviceFile(dev);
00565 if (!device_file.isNull())
00566 {
00567 pDevice = MythCDROM::get(this, device_file, false, m_AllowEject);
00568
00569 if (AddDevice(pDevice))
00570 return true;
00571 }
00572 }
00573 else
00574 {
00575
00576 QString device_file = GetDeviceFile(dev);
00577 if (!device_file.isNull())
00578 {
00579 pDevice = MythHDD::Get(this, device_file, false, false);
00580 if (AddDevice(pDevice))
00581 return true;
00582 }
00583 }
00584
00585 if (pDevice)
00586 pDevice->deleteLater();
00587
00588 return false;
00589 }
00590
00596 void MediaMonitorUnix::CheckDeviceNotifications(void)
00597 {
00598 char buffer[256];
00599 QString qBuffer = "";
00600
00601 if (!m_fifo)
00602 return;
00603
00604 int size = read(m_fifo, buffer, 255);
00605 while (size > 0)
00606 {
00607
00608 buffer[size] = '\0';
00609 qBuffer.append(buffer);
00610 size = read(m_fifo, buffer, 255);
00611 }
00612 const QStringList list = QStringList::split('\n', qBuffer);
00613
00614 QStringList::const_iterator it = list.begin();
00615 for (; it != list.end(); it++)
00616 {
00617 if ((*it).startsWith("add"))
00618 {
00619 QString dev = (*it).section(' ', 1, 1);
00620
00621
00622 QFile removable(dev + "/removable");
00623 if (removable.exists())
00624 {
00625 removable.open(IO_ReadOnly);
00626 int c = removable.getch();
00627 removable.close();
00628
00629 if (c == '1')
00630 FindPartitions((*it).section(' ', 1, 1), true);
00631 }
00632 }
00633 else if ((*it).startsWith("remove"))
00634 {
00635 RemoveDevice((*it).section(' ', 2, 2));
00636 }
00637 }
00638 }