/* * * $Id: k3bdevicemanager.cpp 676188 2007-06-16 08:55:00Z trueg $ * Copyright (C) 2003-2007 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include #include "k3bdevicemanager.h" #include "k3bdevice.h" #include "k3bdeviceglobals.h" #include "k3bscsicommand.h" #include "k3bmmc.h" #include "k3bdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_FREEBSD #include #include #include #endif #include #include #include #include #include #include #include #ifdef HAVE_RESMGR #include #endif #ifdef Q_OS_LINUX /* Fix definitions for 2.5 kernels */ #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,70) typedef unsigned char u8; #endif #undef __STRICT_ANSI__ #include #define __STRICT_ANSI__ #include #include #ifndef SCSI_DISK_MAJOR #define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \ ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR) || \ ((M) >= SCSI_DISK8_MAJOR && (M) <= SCSI_DISK15_MAJOR)) #endif #ifndef SCSI_BLK_MAJOR #define SCSI_BLK_MAJOR(M) \ (SCSI_DISK_MAJOR(M) \ || (M) == SCSI_CDROM_MAJOR) #endif #ifndef SCSI_GENERIC_MAJOR #define SCSI_GENERIC_MAJOR 21 #endif #endif // Q_OS_LINUX #ifdef Q_OS_FREEBSD #include #include #include #endif #ifdef Q_OS_NETBSD #include #endif class K3bDevice::DeviceManager::Private { public: TQPtrList allDevices; TQPtrList cdReader; TQPtrList cdWriter; TQPtrList dvdReader; TQPtrList dvdWriter; TQPtrList bdReader; TQPtrList bdWriter; bool checkWritingModes; }; K3bDevice::DeviceManager::DeviceManager( TQObject* parent, const char* name ) : TQObject( parent, name ) { d = new Private; } K3bDevice::DeviceManager::~DeviceManager() { d->allDevices.setAutoDelete( true ); delete d; } void K3bDevice::DeviceManager::setCheckWritingModes( bool b ) { d->checkWritingModes = b; } K3bDevice::Device* K3bDevice::DeviceManager::deviceByName( const TQString& name ) { return findDevice( name ); } K3bDevice::Device* K3bDevice::DeviceManager::findDevice( int bus, int id, int lun ) { TQPtrListIterator it( d->allDevices ); while( it.current() ) { if( it.current()->scsiBus() == bus && it.current()->scsiId() == id && it.current()->scsiLun() == lun ) return it.current(); ++it; } return 0; } K3bDevice::Device* K3bDevice::DeviceManager::findDevice( const TQString& devicename ) { if( devicename.isEmpty() ) { k3bDebug() << "(K3bDevice::DeviceManager) request for empty device!" << endl; return 0; } TQPtrListIterator it( d->allDevices ); while( it.current() ) { if( it.current()->deviceNodes().contains(devicename) ) return it.current(); ++it; } return 0; } const TQPtrList& K3bDevice::DeviceManager::cdWriter() const { return d->cdWriter; } const TQPtrList& K3bDevice::DeviceManager::cdReader() const { return d->cdReader; } const TQPtrList& K3bDevice::DeviceManager::dvdWriter() const { return d->dvdWriter; } const TQPtrList& K3bDevice::DeviceManager::dvdReader() const { return d->dvdReader; } const TQPtrList& K3bDevice::DeviceManager::blueRayReader() const { return d->bdReader; } const TQPtrList& K3bDevice::DeviceManager::blueRayWriters() const { return d->bdWriter; } const TQPtrList& K3bDevice::DeviceManager::burningDevices() const { return cdWriter(); } const TQPtrList& K3bDevice::DeviceManager::readingDevices() const { return cdReader(); } const TQPtrList& K3bDevice::DeviceManager::allDevices() const { return d->allDevices; } int K3bDevice::DeviceManager::scanBus() { unsigned int numDevs = d->allDevices.count(); #ifdef Q_OS_LINUX LinuxDeviceScan(); #endif #ifdef Q_OS_FREEBSD BSDDeviceScan(); #endif #ifdef Q_OS_NETBSD NetBSDDeviceScan(); #endif return d->allDevices.count() - numDevs; } void K3bDevice::DeviceManager::LinuxDeviceScan() { #ifdef HAVE_RESMGR // // Resmgr device scan // char** resmgrDevices = rsm_list_devices( 0 ); if( resmgrDevices ) { for( int i = 0; resmgrDevices[i]; ++i ) { addDevice( resmgrDevices[i] ); free( resmgrDevices[i] ); } free( resmgrDevices ); } #endif TQFile info("/proc/sys/dev/cdrom/info"); TQString line,devstring; if( info.open(IO_ReadOnly) ) { info.readLine(line,80); // CD-ROM information, Id: cdrom.c 3.12 2000/10/18 info.readLine(line,80); // TQRegExp re("[\t\n:]+"); while( info.readLine( line, 80 ) > 0 ) { if( line.contains("drive name") > 0 ) { int i = 1; TQString dev; while( !(dev = line.section(re, i, i)).isEmpty() ) { if( addDevice( TQString("/dev/%1").arg(dev) ) ) { devstring += dev + "|"; } // according to the LINUX ALLOCATED DEVICES document (http://www.lanana.org/docs/device-list/), // the official device names for SCSI-CDROM's (block major 11) are /dev/sr*, the // prefix /dev/scd instead of /dev/sr has been used as well, and might make more sense. // Since there should be one and only one device node (and maybe several symbolic links) for // each physical device the next line should be better // else if ( dev.startsWith("sr") ) if ( dev.startsWith("sr") ) { if( addDevice(TQString("/dev/%1").arg(dev.replace(TQRegExp("r"),"cd"))) ) devstring += dev + "|"; } ++i; } } break; } info.close(); } else { kdError() << "(K3bDevice::DeviceManager) could not open /proc/sys/dev/cdrom/info" << endl; } // // Scan the generic devices if we have scsi devices // k3bDebug() << "(K3bDevice::DeviceManager) SCANNING FOR GENERIC DEVICES." << endl; for( int i = 0; i < 16; i++ ) { TQString sgDev = resolveSymLink( TQString("/dev/sg%1").arg(i) ); int bus = -1, id = -1, lun = -1; if( determineBusIdLun( sgDev, bus, id, lun ) ) { if( Device* dev = findDevice( bus, id, lun ) ) { dev->m_genericDevice = sgDev; } } } // FIXME: also scan /dev/scsi/hostX.... for devfs without symlinks } void K3bDevice::DeviceManager::NetBSDDeviceScan() { // Generate entries for /dev/cd* devices // Note: As there are only 10 possible /dev/(r)cd devices, // only these will be found. int i; // Whole disk mask (According to cd(4), the AMD64, i386 and BeBox ports use // 'd' as whole-disk partition, the rest uses 'c'.) #if defined(__i386__) || defined (__amd64__) || defined (__bebox__) static const char slicename = 'd'; #else static const char slicename = 'c'; #endif char devicename[11]; // /dev/rcdXd + trailing zero for (i = 0; i < 10; i++ ) // cd(4) claims there are max. 10 CD devices. { snprintf(devicename,11,"/dev/rcd%d%c",i, slicename); addDevice(TQString(devicename)); } } void K3bDevice::DeviceManager::BSDDeviceScan() { // Unfortunately uses lots of FBSD-specific data structures #ifndef Q_OS_FREEBSD // bool bsdspecificcode = false; // assert(bsdspecificcode); #endif #ifdef Q_OS_FREEBSD union ccb ccb; int fd; int need_close = 0; int skip_device = 0; int bus, target, lun; TQString dev1, dev2; if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { k3bDebug() << "couldn't open %s " << XPT_DEVICE << endl; return; } memset(&ccb, 0, sizeof(ccb)); ccb.ccb_h.func_code = XPT_DEV_MATCH; char buffer[100*sizeof(struct dev_match_result)]; ccb.cdm.match_buf_len = 100*sizeof(struct dev_match_result); ccb.cdm.matches = (struct dev_match_result *)buffer; ccb.cdm.num_matches = 0; ccb.cdm.num_patterns = 0; ccb.cdm.pattern_buf_len = 0; do { if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { k3bDebug() << "(BSDDeviceScan) error sending CAMIOCOMMAND ioctl: " << errno << endl; break; } if ((ccb.ccb_h.status != CAM_RETQ_CMP) || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { k3bDebug() << "(BSDDeviceScan) got CAM error " << ccb.ccb_h.status << ", CDM error %d" << ccb.cdm.status << endl; break; } k3bDebug() << "(BSDDeviceScan) number of matches " << (int)ccb.cdm.num_matches << endl; for (int i = 0; i < (int)ccb.cdm.num_matches; i++) { switch (ccb.cdm.matches[i].type) { case DEV_MATCH_DEVICE: { struct device_match_result *dev_result = &ccb.cdm.matches[i].result.device_result; if (dev_result->flags & DEV_RESULT_UNCONFIGURED) { skip_device = 1; break; } else skip_device = 0; if (need_close) { TQString pass = dev1; TQString dev = "/dev/" + dev2; if (dev2.startsWith("pass")) { pass = dev2; dev = "/dev/" + dev1; } #if __FreeBSD_version < 500100 dev += "c"; #endif if (!dev1.isEmpty() && !dev2.isEmpty() && dev.startsWith("/dev/cd")) { Device* device = new Device(dev.latin1()); device->m_bus = bus; device->m_target = target; device->m_lun = lun; device->m_passDevice = "/dev/" + pass; k3bDebug() << "(BSDDeviceScan) add device " << dev << ":" << bus << ":" << target << ":" << lun << endl; addDevice(device); } need_close = 0; dev1=""; dev2=""; } bus = dev_result->path_id; target = dev_result->target_id; lun = dev_result->target_lun; need_close = 1; break; } case DEV_MATCH_PERIPH: { struct periph_match_result *periph_result = &ccb.cdm.matches[i].result.periph_result; if (skip_device != 0) break; if (need_close > 1) dev1 = periph_result->periph_name + TQString::number(periph_result->unit_number); else dev2 = periph_result->periph_name + TQString::number(periph_result->unit_number); need_close++; break; } case DEV_MATCH_BUS : { // bool cannotmatchbus = false; // assert(cannotmatchbus); break; } } } } while ((ccb.ccb_h.status == CAM_RETQ_CMP) && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); if (need_close) { TQString pass = dev1; TQString dev = "/dev/" + dev2; if (dev2.startsWith("pass")) { pass = dev2; dev = "/dev/" + dev1; } #if __FreeBSD_version < 500100 dev += "c"; #endif if (!dev1.isEmpty() && !dev2.isEmpty() && dev.startsWith("/dev/cd")) { Device* device = new Device(dev.latin1()); device->m_bus = bus; device->m_target = target; device->m_lun = lun; device->m_passDevice = "/dev/" + pass; k3bDebug() << "(BSDDeviceScan) add device " << dev << ":" << bus << ":" << target << ":" << lun << endl; addDevice(device); } } close(fd); #endif } void K3bDevice::DeviceManager::printDevices() { k3bDebug() << "Devices:" << endl << "------------------------------" << endl; TQPtrListIterator it( allDevices() ); for( ; *it; ++it ) { Device* dev = *it; k3bDebug() << "Blockdevice: " << dev->blockDeviceName() << endl << "Generic device: " << dev->genericDevice() << endl << "Vendor: " << dev->vendor() << endl << "Description: " << dev->description() << endl << "Version: " << dev->version() << endl << "Write speed: " << dev->maxWriteSpeed() << endl << "Profiles: " << mediaTypeString( dev->supportedProfiles() ) << endl << "Read Cap: " << mediaTypeString( dev->readCapabilities() ) << endl << "Write Cap: " << mediaTypeString( dev->writeCapabilities() ) << endl << "Writing modes: " << writingModeString( dev->writingModes() ) << endl << "Reader aliases: " << dev->deviceNodes().join(", ") << endl << "------------------------------" << endl; } } void K3bDevice::DeviceManager::clear() { // clear current devices d->cdReader.clear(); d->cdWriter.clear(); d->dvdReader.clear(); d->dvdWriter.clear(); d->bdReader.clear(); d->bdWriter.clear(); // to make sure no one crashes lets keep the devices around until the changed // signals return TQPtrList tmp = d->allDevices; tmp.setAutoDelete( true ); d->allDevices.clear(); emit changed( this ); emit changed(); } bool K3bDevice::DeviceManager::readConfig( KConfig* c ) { // // New configuration format since K3b 0.11.94 // for details see saveConfig() // if( !c->hasGroup( "Devices" ) ) return false; c->setGroup( "Devices" ); TQStringList deviceSearchPath = c->readListEntry( "device_search_path" ); for( TQStringList::const_iterator it = deviceSearchPath.constBegin(); it != deviceSearchPath.constEnd(); ++it ) addDevice( *it ); // // Iterate over all devices and check if we have a config entry // for( TQPtrListIterator it( d->allDevices ); *it; ++it ) { K3bDevice::Device* dev = *it; TQString configEntryName = dev->vendor() + " " + dev->description(); TQStringList list = c->readListEntry( configEntryName ); if( !list.isEmpty() ) { k3bDebug() << "(K3bDevice::DeviceManager) found config entry for devicetype: " << configEntryName << endl; dev->setMaxReadSpeed( list[0].toInt() ); if( list.count() > 1 ) dev->setMaxWriteSpeed( list[1].toInt() ); if( list.count() > 2 ) dev->setCdrdaoDriver( list[2] ); if( list.count() > 3 ) dev->setCdTextCapability( list[3] == "yes" ); } } return true; } bool K3bDevice::DeviceManager::saveConfig( KConfig* c ) { // // New configuration format since K3b 0.11.94 // // We save a device search path which contains all device nodes // where devices could be found including the old search path. // This way also for example a manually added USB device will be // found between sessions. // Then we do not save the device settings (writing speed, cdrdao driver) // for every single device but for every device type. // This also makes sure device settings are kept between sessions // c->setGroup( "Devices" ); TQStringList deviceSearchPath = c->readListEntry( "device_search_path" ); // remove duplicate entries (caused by buggy old implementations) TQStringList saveDeviceSearchPath; for( TQStringList::const_iterator it = deviceSearchPath.constBegin(); it != deviceSearchPath.constEnd(); ++it ) if( !saveDeviceSearchPath.contains( *it ) ) saveDeviceSearchPath.append( *it ); for( TQPtrListIterator it( d->allDevices ); *it; ++it ) { K3bDevice::Device* dev = *it; // update device search path if( !saveDeviceSearchPath.contains( dev->blockDeviceName() ) ) saveDeviceSearchPath.append( dev->blockDeviceName() ); // save the device type settings TQString configEntryName = dev->vendor() + " " + dev->description(); TQStringList list; list << TQString::number(dev->maxReadSpeed()) << TQString::number(dev->maxWriteSpeed()) << dev->cdrdaoDriver(); if( dev->cdrdaoDriver() != "auto" ) list << ( dev->cdTextCapable() == 1 ? "yes" : "no" ); else list << "auto"; c->writeEntry( configEntryName, list ); } c->writeEntry( "device_search_path", saveDeviceSearchPath ); c->sync(); return true; } bool K3bDevice::DeviceManager::testForCdrom( const TQString& devicename ) { #ifdef Q_OS_FREEBSD Q_UNUSED(devicename); return true; #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) bool ret = false; int cdromfd = K3bDevice::openDevice( devicename.ascii() ); if (cdromfd < 0) { k3bDebug() << "could not open device " << devicename << " (" << strerror(errno) << ")" << endl; return ret; } // stat the device struct stat cdromStat; if( ::fstat( cdromfd, &cdromStat ) ) return false; if( !S_ISBLK( cdromStat.st_mode) ) { k3bDebug() << devicename << " is no block device" << endl; } else { k3bDebug() << devicename << " is block device (" << (int)(cdromStat.st_rdev & 0xFF) << ")" << endl; #if defined(Q_OS_NETBSD) } { #endif // inquiry // use a 36 bytes buffer since not all devices return the full inquiry struct unsigned char buf[36]; struct inquiry* inq = (struct inquiry*)buf; ::memset( buf, 0, sizeof(buf) ); ScsiCommand cmd( cdromfd ); cmd[0] = MMC_INQUIRY; cmd[4] = sizeof(buf); cmd[5] = 0; if( cmd.transport( TR_DIR_READ, buf, sizeof(buf) ) ) { k3bDebug() << "(K3bDevice::Device) Unable to do inquiry. " << devicename << " is not a cdrom device" << endl; } else if( (inq->p_device_type&0x1f) != 0x5 ) { k3bDebug() << devicename << " seems not to be a cdrom device: " << strerror(errno) << endl; } else { ret = true; k3bDebug() << devicename << " seems to be cdrom" << endl; } } ::close( cdromfd ); return ret; #endif } K3bDevice::Device* K3bDevice::DeviceManager::addDevice( const TQString& devicename ) { #ifdef Q_OS_FREEBSD return 0; #endif K3bDevice::Device* device = 0; // resolve all symlinks TQString resolved = resolveSymLink( devicename ); k3bDebug() << devicename << " resolved to " << resolved << endl; if ( K3bDevice::Device* oldDev = findDevice(resolved) ) { k3bDebug() << "(K3bDevice::DeviceManager) dev " << resolved << " already found" << endl; oldDev->addDeviceNode( devicename ); return 0; } if( !testForCdrom(resolved) ) { #ifdef HAVE_RESMGR // With resmgr we might only be able to open the symlink name. if( testForCdrom(devicename) ) { resolved = devicename; } else { return 0; } #else return 0; #endif } int bus = -1, target = -1, lun = -1; bool scsi = determineBusIdLun( resolved, bus, target, lun ); if(scsi) { if ( K3bDevice::Device* oldDev = findDevice(bus, target, lun) ) { k3bDebug() << "(K3bDevice::DeviceManager) dev " << resolved << " already found" << endl; oldDev->addDeviceNode( devicename ); return 0; } } device = new K3bDevice::Device(resolved); if( scsi ) { device->m_bus = bus; device->m_target = target; device->m_lun = lun; } return addDevice(device); } K3bDevice::Device* K3bDevice::DeviceManager::addDevice( K3bDevice::Device* device ) { const TQString devicename = device->devicename(); if( !device->init() ) { k3bDebug() << "Could not initialize device " << devicename << endl; delete device; return 0; } if( device ) { d->allDevices.append( device ); // not every drive is able to read CDs // there are some 1st generation DVD writer that cannot if( device->type() & K3bDevice::DEVICE_CD_ROM ) d->cdReader.append( device ); if( device->readsDvd() ) d->dvdReader.append( device ); if( device->writesCd() ) d->cdWriter.append( device ); if( device->writesDvd() ) d->dvdWriter.append( device ); if( device->readCapabilities() & MEDIA_BD_ALL ) d->bdReader.append( device ); if( device->writeCapabilities() & MEDIA_BD_ALL ) d->bdWriter.append( device ); if( device->writesCd() ) { // default to max write speed k3bDebug() << "(K3bDevice::DeviceManager) setting current write speed of device " << device->blockDeviceName() << " to " << device->maxWriteSpeed() << endl; device->setCurrentWriteSpeed( device->maxWriteSpeed() ); } emit changed( this ); emit changed(); } return device; } void K3bDevice::DeviceManager::removeDevice( const TQString& dev ) { if( Device* device = findDevice( dev ) ) { d->cdReader.removeRef( device ); d->dvdReader.removeRef( device ); d->bdReader.removeRef( device ); d->cdWriter.removeRef( device ); d->dvdWriter.removeRef( device ); d->bdWriter.removeRef( device ); d->allDevices.removeRef( device ); emit changed( this ); emit changed(); delete device; } } bool K3bDevice::DeviceManager::determineBusIdLun( const TQString& dev, int& bus, int& id, int& lun ) { #ifdef Q_OS_FREEBSD Q_UNUSED(dev); Q_UNUSED(bus); Q_UNUSED(id); Q_UNUSED(lun); return false; /* NOTREACHED */ #endif #ifdef Q_OS_NETBSD int cdromfd = K3bDevice::openDevice ( dev.ascii() ); if (cdromfd < 0) { int local_errno = errno; // For all we know, k3bDebug() destroys errno k3bDebug() << "could not open device " << dev << " (" << strerror(local_errno) << ")" << endl; return false; } struct scsi_addr my_addr; if (::ioctl(cdromfd, SCIOCIDENTIFY, &my_addr)) { int local_errno = errno; // For all we know, k3bDebug() destroys errno k3bDebug() << "ioctl(SCIOCIDENTIFY) failed on device " << dev << " (" << strerror(local_errno) << ")" << endl; ::close(cdromfd); return false; } if (my_addr.type == TYPE_ATAPI) { // XXX Re-map atapibus, so it doesn't conflict with "real" scsi // busses bus = 15; id = my_addr.addr.atapi.drive + 2 * my_addr.addr.atapi.atbus; lun = 0; } else { bus = my_addr.addr.scsi.scbus; id = my_addr.addr.scsi.target; lun = my_addr.addr.scsi.lun; } ::close(cdromfd); return true; #endif #ifdef Q_OS_LINUX int ret = false; int cdromfd = K3bDevice::openDevice( dev.ascii() ); if (cdromfd < 0) { return false; } struct stat cdromStat; if ( ::fstat( cdromfd, &cdromStat ) ) return false; if( SCSI_BLK_MAJOR( cdromStat.st_rdev>>8 ) || SCSI_GENERIC_MAJOR == (cdromStat.st_rdev>>8) ) { struct ScsiIdLun { int id; int lun; }; ScsiIdLun idLun; // in kernel 2.2 SCSI_IOCTL_GET_IDLUN does not contain the bus id if ( (::ioctl( cdromfd, SCSI_IOCTL_GET_IDLUN, &idLun ) < 0) || (::ioctl( cdromfd, SCSI_IOCTL_GET_BUS_NUMBER, &bus ) < 0) ) { k3bDebug() << "Need a filename that resolves to a SCSI device" << endl; ret = false; } else { id = idLun.id & 0xff; lun = (idLun.id >> 8) & 0xff; k3bDebug() << "bus: " << bus << ", id: " << id << ", lun: " << lun << endl; ret = true; } } ::close(cdromfd); return ret; #endif } TQString K3bDevice::DeviceManager::resolveSymLink( const TQString& path ) { char resolved[PATH_MAX]; if( !realpath( TQFile::encodeName(path), resolved ) ) { k3bDebug() << "Could not resolve " << path << endl; return path; } return TQString::fromLatin1( resolved ); } #include "k3bdevicemanager.moc"