/* * * $Id: k3bdevice.cpp 732002 2007-11-02 14:13:14Z 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 "k3bdevice.h" #include "k3bdeviceglobals.h" #include "k3btrack.h" #include "k3btoc.h" #include "k3bdiskinfo.h" #include "k3bmmc.h" #include "k3bscsicommand.h" #include "k3bcrc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,70) typedef unsigned char u8; #endif #undef __STRICT_ANSI__ #include #define __STRICT_ANSI__ #endif // Q_OS_LINUX #ifdef Q_OS_FREEBSD #include #include #define CD_FRAMESIZE_RAW 2352 #endif #ifdef Q_OS_NETBSD #include #endif #ifdef HAVE_RESMGR extern "C" { #include } #endif // // Very evil hacking: force the speed values to be acurate // as long as "they" do not introduce other "broken" DVD // speeds like 2.4 this works fine // static int fixupDvdWritingSpeed( int speed ) { // // Some writers report their speeds in 1000 bytes per second instead of 1024. // if( speed % 1385 == 0 ) return speed; else if( speed % 1352 == 0 ) return speed*1385/1352; // has to be 2.4x speed else return 3324; } const char* K3bDevice::Device::cdrdao_drivers[] = { "auto", "plextor", "plextor-scan", "cdd2600", "generic-mmc", "generic-mmc-raw", "ricoh-mp6200", "sony-cdu920", "sony-cdu948", "taiyo-yuden", "teac-cdr55", "toshiba", "yamaha-cdr10x", 0 }; #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) int K3bDevice::openDevice( const char* name, bool write ) { int fd = -1; int flags = O_NONBLOCK; if( write ) flags |= O_RDWR; else flags |= O_RDONLY; #ifdef HAVE_RESMGR // first try resmgr fd = ::rsm_open_device( name, flags ); // k3bDebug() << "(K3bDevice::Device) resmgr open: " << fd << endl; #endif if( fd < 0 ) fd = ::open( name, flags ); if( fd < 0 ) { k3bDebug() << "(K3bDevice::Device) could not open device " << name << ( write ? " for writing" : " for reading" ) << endl; k3bDebug() << " (" << strerror(errno) << ")" << endl; fd = -1; // at least open it read-only (which is sufficient for kernels < 2.6.8 anyway) if( write ) return openDevice( name, false ); } return fd; } #endif class K3bDevice::Device::Private { public: Private() : supportedProfiles(0), #ifdef Q_OS_LINUX deviceFd(-1), #endif #ifdef Q_OS_NETBSD deviceFd(-1), #endif #ifdef Q_OS_FREEBSD cam(0), #endif openedReadWrite(false), burnfree(false) { } int readCapabilities; int writeCapabilities; int supportedProfiles; TQStringList allNodes; #ifdef Q_OS_LINUX int deviceFd; #endif #ifdef Q_OS_NETBSD int deviceFd; #endif #ifdef Q_OS_FREEBSD struct cam_device *cam; #endif bool openedReadWrite; bool burnfree; TQMutex mutex; TQMutex openCloseMutex; }; K3bDevice::Device::Device( const TQString& devname ) : m_bus(-1), m_target(-1), m_lun(-1), m_writeModes(0) { d = new Private; m_blockDevice = devname; d->allNodes.append(devname); m_cdrdaoDriver = "auto"; m_cdTextCapable = 0; m_maxWriteSpeed = 0; m_maxReadSpeed = 0; d->burnfree = false; m_dvdMinusTestwrite = true; m_bufferSize = 0; } K3bDevice::Device::~Device() { close(); delete d; } bool K3bDevice::Device::init( bool bCheckWritingModes ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": init()" << endl; // // they all should read CD-ROM. // d->readCapabilities = MEDIA_CD_ROM; d->writeCapabilities = 0; d->supportedProfiles = 0; if( !open() ) return false; // // inquiry // use a 36 bytes buffer since not all devices return the full inquiry struct // ScsiCommand cmd( this ); unsigned char buf[36]; cmd.clear(); ::memset( buf, 0, sizeof(buf) ); struct inquiry* inq = (struct inquiry*)buf; cmd[0] = MMC_INQUIRY; cmd[4] = sizeof(buf); cmd[5] = 0; if( cmd.transport( TR_DIR_READ, buf, sizeof(buf) ) ) { kdError() << "(K3bDevice::Device) Unable to do inquiry." << endl; close(); return false; } else { m_vendor = TQString::fromLatin1( (char*)(inq->vendor), 8 ).stripWhiteSpace(); m_description = TQString::fromLatin1( (char*)(inq->product), 16 ).stripWhiteSpace(); m_version = TQString::fromLatin1( (char*)(inq->revision), 4 ).stripWhiteSpace(); } if( m_vendor.isEmpty() ) m_vendor = "UNKNOWN"; if( m_description.isEmpty() ) m_description = "UNKNOWN"; // // We probe all features of the device. Since not all devices support the GET CONFIGURATION command // we also query the mode page 2A and use the cdrom.h stuff to get as much information as possible // checkFeatures(); // // Check the supported write modes (WRITINGMODE_TAO, WRITINGMODE_SAO, WRITINGMODE_RAW) by trying to set them // We do this before checking mode page 2A in case some readers allow changin // the write parameter page // if( bCheckWritingModes ) checkWritingModes(); // // Most current drives support the 2A mode page // Here we can get some more information (cdrecord -prcap does exactly this) // checkFor2AFeatures(); m_maxWriteSpeed = determineMaximalWriteSpeed(); // // Check Just-Link via Ricoh mode page 0x30 // if( !d->burnfree ) checkForJustLink(); // // Support for some very old drives // checkForAncientWriters(); // // If it can be written it can also be read // d->readCapabilities |= d->writeCapabilities; close(); return furtherInit(); } bool K3bDevice::Device::furtherInit() { #ifdef Q_OS_LINUX // // Since all CDR drives at least support WRITINGMODE_TAO, all CDRW drives should support // mode page 2a and all DVD writer should support mode page 2a or the GET CONFIGURATION // command this is redundant and may be removed for BSD ports or even completely // // We just keep it here because of the "should" in the sentence above. If someone can tell me // that the linux driver does nothing more we can remove it completely. // open(); int drivetype = ::ioctl( handle(), CDROM_GET_CAPABILITY, CDSL_CURRENT ); if( drivetype < 0 ) { k3bDebug() << "Error while retrieving capabilities." << endl; close(); return false; } d->readCapabilities |= DEVICE_CD_ROM; if( drivetype & CDC_CD_R ) d->writeCapabilities |= MEDIA_CD_R; if( drivetype & CDC_CD_RW ) d->writeCapabilities |= MEDIA_CD_RW; if( drivetype & CDC_DVD_R ) d->writeCapabilities |= MEDIA_DVD_R; if( drivetype & CDC_DVD ) d->readCapabilities |= MEDIA_DVD_ROM; close(); #endif // Q_OS_LINUX return true; } void K3bDevice::Device::checkForAncientWriters() { // TODO: add a boolean which determines if this device is non-MMC so we may warn the user at K3b startup about it // // There are a lot writers out there which behave like the TEAC R5XS // if( ( vendor().startsWith("TEAC") && ( description().startsWith("CD-R50S") || description().startsWith("CD-R55S") ) ) || ( vendor().startsWith("SAF") && ( description().startsWith("CD-R2006PLUS") || description().startsWith("CD-RW226") || description().startsWith("CD-R4012") ) ) || ( vendor().startsWith("JVC") && ( description().startsWith("XR-W2001") || description().startsWith("XR-W2010") || description().startsWith("R2626") ) ) || ( vendor().startsWith("PINNACLE") && ( description().startsWith("RCD-1000") || description().startsWith("RCD5020") || description().startsWith("RCD5040") || description().startsWith("RCD 4X4") ) ) || ( vendor().startsWith("Traxdata") && description().startsWith("CDR4120") ) ) { m_writeModes = WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 4; m_maxReadSpeed = 12; m_bufferSize = 1024; d->burnfree = false; } else if( vendor().startsWith("TEAC") ) { if( description().startsWith("CD-R56S") ) { m_writeModes |= TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 6; m_maxReadSpeed = 24; m_bufferSize = 1302; d->burnfree = false; } if( description().startsWith("CD-R58S") ) { m_writeModes |= TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 8; m_maxReadSpeed = 24; m_bufferSize = 4096; d->burnfree = false; } } else if( vendor().startsWith("MATSHITA") ) { if( description().startsWith("CD-R CW-7501") ) { m_writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 2; m_maxReadSpeed = 4; m_bufferSize = 1024; d->burnfree = false; } if( description().startsWith("CD-R CW-7502") ) { m_writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 4; m_maxReadSpeed = 8; m_bufferSize = 1024; d->burnfree = false; } else if( description().startsWith("CD-R56S") ) { m_writeModes |= WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 6; m_maxReadSpeed = 24; m_bufferSize = 1302; d->burnfree = false; } } else if( vendor().startsWith("HP") ) { if( description().startsWith("CD-Writer 6020") ) { m_writeModes = WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 2; m_maxReadSpeed = 6; m_bufferSize = 1024; d->burnfree = false; } } else if( vendor().startsWith( "PHILIPS" ) ) { if( description().startsWith( "CDD2600" ) ) { m_writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; m_maxWriteSpeed = 2; m_maxReadSpeed = 6; m_bufferSize = 1024; d->burnfree = false; } } } K3bDevice::Interface K3bDevice::Device::interfaceType() const { if( m_bus != -1 && m_target != -1 && m_lun != -1 ) return SCSI; else return IDE; } bool K3bDevice::Device::dao() const { return m_writeModes & WRITINGMODE_SAO; } bool K3bDevice::Device::supportsRawWriting() const { return( writingModes() & (WRITINGMODE_RAW|WRITINGMODE_RAW_R16|WRITINGMODE_RAW_R96P|WRITINGMODE_RAW_R96R) ); } bool K3bDevice::Device::writesCd() const { return ( d->writeCapabilities & MEDIA_CD_R ) && ( m_writeModes & WRITINGMODE_TAO ); } bool K3bDevice::Device::burner() const { return ( writesCd() || writesDvd() ); } bool K3bDevice::Device::writesCdrw() const { return d->writeCapabilities & MEDIA_CD_RW; } bool K3bDevice::Device::writesDvd() const { return ( writesDvdPlus() || writesDvdMinus() ); } bool K3bDevice::Device::writesDvdPlus() const { return d->writeCapabilities & (MEDIA_DVD_PLUS_R|MEDIA_DVD_PLUS_RW); } bool K3bDevice::Device::writesDvdMinus() const { return d->writeCapabilities & (MEDIA_DVD_R|MEDIA_DVD_RW); } bool K3bDevice::Device::readsDvd() const { return d->readCapabilities & MEDIA_DVD_ROM; } int K3bDevice::Device::type() const { int r = 0; if( readCapabilities() & MEDIA_CD_ROM ) r |= DEVICE_CD_ROM; if( writeCapabilities() & MEDIA_CD_R ) r |= DEVICE_CD_R; if( writeCapabilities() & MEDIA_CD_RW ) r |= DEVICE_CD_RW; if( readCapabilities() & MEDIA_DVD_ROM ) r |= DEVICE_DVD_ROM; if( writeCapabilities() & MEDIA_DVD_RAM ) r |= DEVICE_DVD_RAM; if( writeCapabilities() & MEDIA_DVD_R ) r |= DEVICE_DVD_R; if( writeCapabilities() & MEDIA_DVD_RW ) r |= DEVICE_DVD_RW; if( writeCapabilities() & MEDIA_DVD_R_DL ) r |= DEVICE_DVD_R_DL; if( writeCapabilities() & MEDIA_DVD_PLUS_R ) r |= DEVICE_DVD_PLUS_R; if( writeCapabilities() & MEDIA_DVD_PLUS_RW ) r |= DEVICE_DVD_PLUS_RW; if( writeCapabilities() & MEDIA_DVD_PLUS_R_DL ) r |= DEVICE_DVD_PLUS_R_DL; if( readCapabilities() & MEDIA_HD_DVD_ROM ) r |= DEVICE_HD_DVD_ROM; if( writeCapabilities() & MEDIA_HD_DVD_R ) r |= DEVICE_HD_DVD_R; if( writeCapabilities() & MEDIA_HD_DVD_RAM ) r |= DEVICE_HD_DVD_RAM; if( readCapabilities() & MEDIA_BD_ROM ) r |= DEVICE_BD_ROM; if( writeCapabilities() & MEDIA_BD_R ) r |= DEVICE_BD_R; if( writeCapabilities() & MEDIA_BD_RE ) r |= DEVICE_BD_RE; return r; } int K3bDevice::Device::readCapabilities() const { return d->readCapabilities; } int K3bDevice::Device::writeCapabilities() const { return d->writeCapabilities; } const TQString& K3bDevice::Device::devicename() const { return blockDeviceName(); } TQString K3bDevice::Device::busTargetLun() const { return TQString("%1,%2,%3").arg(m_bus).arg(m_target).arg(m_lun); } int K3bDevice::Device::cdTextCapable() const { if( cdrdaoDriver() == "auto" ) return 0; else return m_cdTextCapable; } void K3bDevice::Device::setCdTextCapability( bool b ) { m_cdTextCapable = ( b ? 1 : 2 ); } bool K3bDevice::Device::burnproof() const { return burnfree(); } bool K3bDevice::Device::burnfree() const { return d->burnfree; } bool K3bDevice::Device::isDVD() const { if( readsDvd() ) return( mediaType() & MEDIA_DVD_ALL ); else return false; } int K3bDevice::Device::isEmpty() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); int ret = STATE_UNKNOWN; if( !open() ) return STATE_UNKNOWN; if( !testUnitReady() ) return STATE_NO_MEDIA; unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; switch( inf->status ) { case 0: ret = STATE_EMPTY; break; case 1: ret = STATE_INCOMPLETE; break; case 2: ret = STATE_COMPLETE; break; default: ret = STATE_UNKNOWN; break; } delete [] data; } if( needToClose ) close(); return ret; } int K3bDevice::Device::numSessions() const { // // Session Info // ============ // Byte 0-1: Data Length // Byte 2: First Complete Session Number (Hex) - always 1 // Byte 3: Last Complete Session Number (Hex) // int ret = -1; unsigned char* data = 0; unsigned int len = 0; if( mediaType() & MEDIA_CD_ALL ) { // // Althought disk_info should get the real value without ide-scsi // I keep getting wrong values (the value is too high. I think the leadout // gets counted as session sometimes :() // if( readTocPmaAtip( &data, len, 1, 0, 0 ) ) { ret = data[3]; delete [] data; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": could not get session info !" << endl; } } else { if( readDiscInformation( &data, len ) ) { ret = (int)( data[9]<<8 | data[4] ); // do only count complete sessions if( (data[2]>>2) != 3 ) ret--; delete [] data; } } return ret; } int K3bDevice::Device::getDataMode( const K3b::Msf& sector ) const { bool needToClose = !isOpen(); int ret = Track::UNKNOWN; if( !open() ) return ret; // we use readCdMsf here since it's defined mandatory in MMC1 and // we only use this method for CDs anyway unsigned char data[2352]; bool readSuccess = readCdMsf( data, 2352, 0, // all sector types false, // no dap sector, sector+1, true, // SYNC true, // HEADER true, // SUBHEADER true, // USER DATA true, // EDC/ECC 0, // no c2 info 0 ); if( readSuccess ) { if ( data[15] == 0x1 ) ret = Track::MODE1; else if ( data[15] == 0x2 ) ret = Track::MODE2; if ( ret == Track::MODE2 ) { if ( data[16] == data[20] && data[17] == data[21] && data[18] == data[22] && data[19] == data[23] ) { if ( data[18] & 0x20 ) ret = Track::XA_FORM2; else ret = Track::XA_FORM1; } } } if( needToClose ) close(); return ret; } int K3bDevice::Device::getTrackDataMode( const K3bDevice::Track& track ) const { return getDataMode( track.firstSector() ); } K3bDevice::Toc K3bDevice::Device::readToc() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); Toc toc; if( !open() ) return toc; int mt = mediaType(); // // Use the profile if available because DVD-ROM units need to treat DVD+-R(W) media as DVD-ROM // if supported at all // if( currentProfile() == MEDIA_DVD_ROM ) mt = MEDIA_DVD_ROM; if( mt & (MEDIA_DVD_MINUS_ALL|MEDIA_DVD_PLUS_RW|MEDIA_DVD_ROM) ) { if( !readFormattedToc( toc, mt ) ) { K3b::Msf size; if( readCapacity( size ) ) { Track track; track.m_firstSector = 0; track.m_lastSector = size.lba(); track.m_session = 1; track.m_type = Track::DATA; track.m_mode = Track::DVD; track.m_copyPermitted = ( mt != MEDIA_DVD_ROM ); track.m_preEmphasis = ( mt != MEDIA_DVD_ROM ); toc.append( track ); } else k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << "READ CAPACITY for toc failed." << endl; } } else if( mt & (MEDIA_DVD_PLUS_R|MEDIA_DVD_PLUS_R_DL) ) { // // a DVD+R disk may have multiple sessions // every session may contain up to 16 fragments // if the disk is open there is one open session // every closed session is viewed as a track whereas // every fragment of the open session is viewed as a track // // We may use // READ DISK INFORMATION // READ TRACK INFORMATION: track number FFh, however, does not refer to the invisible track // READ TOC/PMA/ATIP: form 0 refers to all closed sessions // form 1 refers to the last closed session // readFormattedToc( toc, mt ); } else if( mt & MEDIA_BD_ALL ) { readFormattedToc( toc, mt ); } else if( mt == MEDIA_DVD_RAM ) { k3bDebug() << "(K3bDevice::readDvdToc) no dvdram support" << endl; } else if( mt & MEDIA_CD_ALL ) { bool success = readRawToc( toc ); if( !success ) { success = readFormattedToc( toc, mt ); #ifdef Q_OS_LINUX if( !success ) { k3bDebug() << "(K3bDevice::Device) MMC READ TOC failed. falling back to cdrom.h." << endl; readTocLinux(toc); } #endif if( success ) fixupToc( toc ); } } if( needToClose ) close(); return toc; } void K3bDevice::Device::readIsrcMcn( K3bDevice::Toc& toc ) const { // read MCN and ISRC of all tracks TQCString mcn; if( readMcn( mcn ) ) { toc.setMcn( mcn ); k3bDebug() << "(K3bDevice::Device) found MCN: " << mcn << endl; } else k3bDebug() << "(K3bDevice::Device) no MCN found." << endl; for( unsigned int i = 1; i <= toc.count(); ++i ) { TQCString isrc; if( toc[i-1].type() == Track::AUDIO ) { if( readIsrc( i, isrc ) ) { k3bDebug() << "(K3bDevice::Device) found ISRC for track " << i << ": " << isrc << endl; toc[i-1].setIsrc( isrc ); } else k3bDebug() << "(K3bDevice::Device) no ISRC found for track " << i << endl; } } } bool K3bDevice::Device::readFormattedToc( K3bDevice::Toc& toc, int mt ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = false; toc.clear(); unsigned int lastTrack = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( !(mt & MEDIA_CD_ALL) ) { // // on DVD-R(W) multisession disks only two sessions are represented as tracks in the readTocPmaAtip // response (fabricated TOC). Thus, we use readDiscInformation for DVD media to get the proper number of tracks // if( readDiscInformation( &data, dataLen ) ) { lastTrack = (int)( data[11]<<8 | data[6] ); delete [] data; if( readTrackInformation( &data, dataLen, 1, lastTrack ) ) { track_info_t* trackInfo = (track_info_t*)data; if( trackInfo->blank ) { lastTrack--; } delete [] data; success = true; } else return false; } else return false; } else { if( readTocPmaAtip( &data, dataLen, 0, 0, 1 ) ) { if( dataLen < 4 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": formatted toc data too small." << endl; } else if( dataLen != ( (unsigned int)sizeof(toc_track_descriptor) * ((unsigned int)data[3]+1) ) + 4 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": invalid formatted toc data length: " << (dataLen-2) << endl; } else { lastTrack = data[3]; toc_track_descriptor* td = (toc_track_descriptor*)&data[4]; for( unsigned int i = 0; i < lastTrack; ++i ) { Track track; unsigned int control = 0; // // In case READ TRACK INFORMATION fails: // no session number info // no track length and thus possibly incorrect last sector for // multisession disks // track.m_firstSector = from4Byte( td[i].start_adr ); track.m_lastSector = from4Byte( td[i+1].start_adr ) - 1; control = td[i].control; track.m_type = (control & 0x4) ? Track::DATA : Track::AUDIO; track.m_mode = getTrackDataMode( track ); track.m_copyPermitted = ( control & 0x2 ); track.m_preEmphasis = ( control & 0x1 ); toc.append( track ); } success = true; } delete [] data; } } // // Try to get information for all the tracks // for( unsigned int i = 0; i < lastTrack; ++i ) { if( toc.count() < i+1 ) toc.append( Track() ); unsigned char* trackData = 0; unsigned int trackDataLen = 0; if( readTrackInformation( &trackData, trackDataLen, 1, i+1 ) ) { track_info_t* trackInfo = (track_info_t*)trackData; toc[i].m_firstSector = from4Byte( trackInfo->track_start ); if( i > 0 && toc[i-1].m_lastSector == 0 ) toc[i-1].m_lastSector = toc[i].m_firstSector - 1; // There are drives that return 0 track length here! // Some drives even return an invalid length here. :( if( from4Byte( trackInfo->track_size ) > 0 ) toc[i].m_lastSector = toc[i].m_firstSector + from4Byte( trackInfo->track_size ) - 1; if( trackInfo->nwa_v ) { toc[i].m_nextWritableAddress = from4Byte( trackInfo->next_writable ); toc[i].m_freeBlocks = from4Byte( trackInfo->free_blocks ); } toc[i].m_session = (int)(trackInfo->session_number_m<<8 & 0xf0 | trackInfo->session_number_l & 0x0f); //FIXME: is this BCD? int control = trackInfo->track_mode; if( mt & MEDIA_CD_ALL ) { toc[i].m_type = (control & 0x4) ? Track::DATA : Track::AUDIO; toc[i].m_mode = getTrackDataMode( toc[i] ); } else { toc[i].m_type = Track::DATA; toc[i].m_mode = Track::DVD; } toc[i].m_copyPermitted = ( control & 0x2 ); toc[i].m_preEmphasis = ( control & 0x1 ); delete [] trackData; } else if( !(mt & MEDIA_CD_ALL) ) { success = false; } } // this can only happen with DVD media if( !toc.isEmpty() && toc.last().lastSector() == 0 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " no track length for the last non-empty track." << endl; unsigned char* trackData = 0; unsigned int trackDataLen = 0; if( readTrackInformation( &trackData, trackDataLen, 1, lastTrack+1 ) ) { track_info_t* trackInfo = (track_info_t*)trackData; toc.last().m_lastSector = from4Byte( trackInfo->track_start ) - 1; delete [] trackData; } } if( needToClose ) close(); return success; } bool K3bDevice::Device::readRawToc( K3bDevice::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = false; toc.clear(); if( open() ) { // // Read Raw TOC (format: 0010b) // // For POINT from 01h-63h we get all the tracks // POINT a1h gices us the last track number in the session in PMIN // POINT a2h gives the start of the session lead-out in PMIN,PSEC,PFRAME // unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 2, false, 1 ) ) { if( dataLen > 4 ) { success = true; toc_raw_track_descriptor* tr = (toc_raw_track_descriptor*)&data[4]; // // debug the raw toc data // k3bDebug() << "Session | ADR | CONTROL| TNO | POINT | Min | Sec | Frame | Zero | PMIN | PSEC | PFRAME |" << endl; for( unsigned int i = 0; i < (dataLen-4)/(int)sizeof(toc_raw_track_descriptor); ++i ) { TQString s; s += TQString( " %1 |" ).arg( (int)tr[i].session_number, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].adr, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].control, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].tno, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].point, 6, 16 ); s += TQString( " %1 |" ).arg( (int)tr[i].min, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].sec, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].frame, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].zero, 6, 16 ); s += TQString( " %1 |" ).arg( (int)tr[i].p_min, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].p_sec, 6 ); s += TQString( " %1 |" ).arg( (int)tr[i].p_frame, 6 ); k3bDebug() << s << endl; } // // First we try to determine if the raw toc data uses BCD values // int isBcd = rawTocDataWithBcdValues( data, dataLen ); if( isBcd == -1 ) { delete [] data; return false; } K3b::Msf sessionLeadOut; for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0x63 ) { // track K3bTrack track; track.m_session = tr[i].session_number; // :( We use 00:00:00 == 0 lba) if( isBcd ) track.m_firstSector = K3b::Msf( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ) - 150; else track.m_firstSector = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150; track.m_type = ( tr[i].control & 0x4 ? Track::DATA : Track::AUDIO ); track.m_mode = ( track.type() == Track::DATA ? getTrackDataMode(track) : Track::UNKNOWN ); track.m_copyPermitted = ( tr[i].control & 0x2 ); track.m_preEmphasis = ( tr[i].control & 0x1 ); // // only do this within a session because otherwise we already set the last sector with the session leadout // if( !toc.isEmpty() ) if( toc[toc.count()-1].session() == track.session() ) toc[toc.count()-1].m_lastSector = track.firstSector() - 1; toc.append(track); } else if( tr[i].point == 0xa2 ) { // // since the session is always reported before the tracks this is where we do this: // set the previous session's last tracks's last sector to the first sector of the // session leadout (which was reported before the tracks) // // This only happens on multisession CDs // if( !toc.isEmpty() ) toc[toc.count()-1].m_lastSector = sessionLeadOut - 1; // this is save since the descriptors are reported in ascending order of the session number // :( We use 00:00:00 == 0 lba) if( isBcd ) sessionLeadOut = K3b::Msf( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ) - 150; else sessionLeadOut = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150; } } k3bDebug() << blockDeviceName() << ": setting last sector of last track to " << (sessionLeadOut-1).lba() << endl; // set the last track's last sector if( !toc.isEmpty() ) toc[toc.count()-1].m_lastSector = sessionLeadOut - 1; } else k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " empty raw toc." << endl; delete [] data; } } if( needToClose ) close(); return success; } int K3bDevice::Device::rawTocDataWithBcdValues( unsigned char* data, unsigned int dataLen ) const { toc_raw_track_descriptor* tr = (toc_raw_track_descriptor*)&data[4]; bool notBcd = false; bool notHex = false; // // in most cases this will already tell us if a drive does not provide bcd numbers // (which should be all newer MMC drives) // for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0xa2) { if( !K3bDevice::isValidBcd(tr[i].p_min) || !K3bDevice::isValidBcd(tr[i].p_sec) || !K3bDevice::isValidBcd(tr[i].p_frame) ) { notBcd = true; break; } // we only need to check sec and frame since min needs to be <= 99 // and bcd values are never bigger than 99. else if( (int)K3bDevice::fromBcd(tr[i].p_sec) >= 60 || (int)K3bDevice::fromBcd(tr[i].p_frame) >= 75 ) { notBcd = true; break; } } } // // all values are valid bcd values but we still don't know for sure if they are really // used as bcd. So we also check the HEX values. // for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0xa2 ) { if( (int)tr[i].p_min > 99 || (int)tr[i].p_sec >= 60 || (int)tr[i].p_frame >= 75 ) { notHex = true; break; } } } // // If all values are valid bcd and valid hex we check the start sectors of the tracks. // if( !notHex || !notBcd ) { K3b::Msf sessionLeadOutHex, sessionLeadOutBcd; K3b::Msf lastTrackHex, lastTrackBcd; for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 ) { if( tr[i].point < 0x64 ) { // check hex values if( K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) < lastTrackHex ) notHex = true; // check bcd values if( K3b::Msf( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ) < lastTrackBcd ) notBcd = true; lastTrackBcd = K3b::Msf( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ); lastTrackHex = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); } else if( tr[i].point == 0xa2 ) { if( sessionLeadOutHex < lastTrackHex ) notHex = true; if( sessionLeadOutBcd < lastTrackBcd ) notBcd = true; sessionLeadOutHex = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); sessionLeadOutBcd = K3b::Msf( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ); } } } // check the last track if( sessionLeadOutHex < lastTrackHex ) notHex = true; if( sessionLeadOutBcd < lastTrackBcd ) notBcd = true; } if( !notBcd && !notHex ) { k3bDebug() << "(K3bDevice::Device) need to compare raw toc to formatted toc. :(" << endl; // // All values are valid bcd and valid HEX values so we compare with the formatted toc. // This slows us down a lot but in most cases this should not be reached anyway. // // TODO: also check the bcd values // K3bDevice::Toc formattedToc; if( readFormattedToc( formattedToc, MEDIA_CD_ROM ) ) { for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point < 0x64 ) { unsigned int track = (int)tr[i].point; // FIXME: do bcd drive also encode the track number in bcd? If so test it, too. if( track > formattedToc.count() ) { notHex = true; break; } K3b::Msf posHex( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); K3b::Msf posBcd( K3bDevice::fromBcd(tr[i].p_min), K3bDevice::fromBcd(tr[i].p_sec), K3bDevice::fromBcd(tr[i].p_frame) ); posHex -= 150; posBcd -= 150; if( posHex != formattedToc[track-1].firstSector() ) notHex = true; if( posBcd != formattedToc[track-1].firstSector() ) notBcd = true; } } } } if( notBcd ) k3bDebug() << "(K3bDevice::Device) found invalid bcd values. No bcd toc." << endl; if( notHex ) k3bDebug() << "(K3bDevice::Device) found invalid hex values. No hex toc." << endl; if( notBcd == notHex ) { k3bDebug() << "(K3bDevice::Device) unable to determine if hex (" << notHex << ") or bcd (" << notBcd << ")." << endl; if( !notHex ) { k3bDebug() << "Assuming hex encoding in favor of newer drives and the more reliable raw toc." << endl; return 0; } return -1; } else if( notBcd ) return 0; else return 1; } K3bDevice::CdText K3bDevice::Device::readCdText() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); K3bDevice::CdText textData; if( open() ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 5, false, 0 ) ) { textData.setRawPackData( data, dataLen ); delete [] data; } if( needToClose ) close(); } return textData; } #ifdef Q_OS_LINUX // fallback bool K3bDevice::Device::readTocLinux( K3bDevice::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = true; toc.clear(); struct cdrom_tochdr tochdr; struct cdrom_tocentry tocentry; usageLock(); if( open() ) { // // CDROMREADTOCHDR ioctl returns: // cdth_trk0: First Track Number // cdth_trk1: Last Track Number // if( ::ioctl( d->deviceFd, CDROMREADTOCHDR, &tochdr ) ) { k3bDebug() << "(K3bDevice::Device) could not get toc header !" << endl; success = false; } else { Track lastTrack; for (int i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1 + 1; i++) { ::memset(&tocentry,0,sizeof (struct cdrom_tocentry)); // get Lead-Out Information too tocentry.cdte_track = (i<=tochdr.cdth_trk1) ? i : CDROM_LEADOUT; tocentry.cdte_format = CDROM_LBA; // // CDROMREADTOCENTRY ioctl returns: // cdte_addr.lba: Start Sector Number (LBA Format requested) // cdte_ctrl: 4 ctrl bits // 00x0b: 2 audio Channels(no pre-emphasis) // 00x1b: 2 audio Channels(pre-emphasis) // 10x0b: audio Channels(no pre-emphasis),reserved in cd-rw // 10x1b: audio Channels(pre-emphasis),reserved in cd-rw // 01x0b: data track, recorded uninterrupted // 01x1b: data track, recorded incremental // 11xxb: reserved // xx0xb: digital copy prohibited // xx1xb: digital copy permitted // cdte_addr: 4 addr bits (type of Q-Subchannel data) // 0000b: no Information // 0001b: current position data // 0010b: MCN // 0011b: ISRC // 0100b-1111b: reserved // cdte_datamode: 0: Data Mode1 // 1: CD-I // 2: CD-XA Mode2 // if( ::ioctl( d->deviceFd, CDROMREADTOCENTRY, &tocentry ) ) { k3bDebug() << "(K3bDevice::Device) error reading tocentry " << i << endl; success = false; break; } int startSec = tocentry.cdte_addr.lba; int control = tocentry.cdte_ctrl & 0x0f; int mode = tocentry.cdte_datamode; if( i > tochdr.cdth_trk0 ) { Track track( lastTrack.firstSector(), startSec-1, lastTrack.type(), lastTrack.mode() ); track.m_preEmphasis = control & 0x1; track.m_copyPermitted = control & 0x2; toc.append( track ); } int trackType = 0; int trackMode = Track::UNKNOWN; if( (control & 0x04 ) && (tocentry.cdte_track != CDROM_LEADOUT) ) { trackType = Track::DATA; if( mode == 1 ) trackMode = Track::MODE1; else if( mode == 2 ) trackMode = Track::MODE2; mode = getDataMode(startSec); if( mode != Track::UNKNOWN ) trackMode = mode; } else trackType = Track::AUDIO; lastTrack = Track( startSec, startSec, trackType, trackMode ); } } if( needToClose ) close(); } else success = false; usageUnlock(); return success; } #endif // Q_OS_LINUX bool K3bDevice::Device::fixupToc( K3bDevice::Toc& toc ) const { bool success = false; // // This is a very lame method of fixing the TOC of an Advanced Audio CD // (a CD with two sessions: one with audio tracks and one with the data track) // If a drive does not support reading raw toc or reading track info we only // get every track's first sector. But between sessions there is a gap which is used // for ms stuff. In this case it's 11400 sectors in size. When ripping ausio we would // include these 11400 sectors which would result in a strange ending audio file. // if( numSessions() > 1 || toc.contentType() == MIXED ) { k3bDebug() << "(K3bDevice::Device) fixup multisession toc..." << endl; // // we need to update the last sector of every last track in every session // for now we only update the track before the last session... // This is the most often case: Advanced Audio CD // unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 1, false, 0 ) ) { // // data[6] - first track number in last complete session // data[8-11] - start address of first track in last session // toc[(unsigned int)data[6]-2].m_lastSector = from4Byte( &data[8] ) - 11400 - 1; delete [] data; success = true; } else k3bDebug() << "(K3bDevice::Device) FIXUP TOC failed." << endl; } return success; } bool K3bDevice::Device::block( bool b ) const { // // For some reason the Scsi Command does not work here. // So we use the ioctl on Linux systems // #if defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { success = ( ::ioctl( d->deviceFd, CDROM_LOCKDOOR, b ? 1 : 0 ) == 0 ); if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_NETBSD) bool success = false; bool needToClose = !isOpen(); int arg = b ? 1 : 0; usageLock(); if( open() ) { success = ( ::ioctl( d->deviceFd, DIOCLOCK, &arg ) == 0 ); if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif ScsiCommand cmd( this ); cmd[0] = MMC_PREVENT_ALLOW_MEDIUM_REMOVAL; cmd[5] = 0; // Necessary to set the proper command length if( b ) cmd[4] = 0x01; int r = cmd.transport( TR_DIR_WRITE ); if( r ) k3bDebug() << "(K3bDevice::Device) MMC ALLOW MEDIA REMOVAL failed." << endl; return ( r == 0 ); } bool K3bDevice::Device::rewritable() const { unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; bool e = inf->erasable; delete [] data; return e; } else return false; } bool K3bDevice::Device::eject() const { #ifdef Q_OS_NETBSD bool success = false; bool needToClose = !isOpen(); int arg = 0; usageLock(); if( open() ) { if ( ::ioctl( d->deviceFd, DIOCEJECT, &arg ) >= 0) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { if( ::ioctl( d->deviceFd, CDROMEJECT ) >= 0 ) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif ScsiCommand cmd( this ); cmd[0] = MMC_PREVENT_ALLOW_MEDIUM_REMOVAL; cmd[5] = 0; // Necessary to set the proper command length cmd.transport(); cmd[0] = MMC_START_STOP_UNIT; cmd[5] = 0; // Necessary to set the proper command length cmd[4] = 0x1; // Start unit cmd.transport(); cmd[4] = 0x2; // LoEj = 1, Start = 0 return !cmd.transport(); } bool K3bDevice::Device::load() const { #ifdef Q_OS_NETBSD bool success = false; bool needToClose = !isOpen(); int arg = 0; usageLock(); if( open() ) { if ( ::ioctl( d->deviceFd, CDIOCCLOSE, &arg ) >= 0) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { if( ::ioctl( d->deviceFd, CDROMCLOSETRAY ) >= 0 ) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif ScsiCommand cmd( this ); cmd[0] = MMC_START_STOP_UNIT; cmd[4] = 0x3; // LoEj = 1, Start = 1 cmd[5] = 0; // Necessary to set the proper command length return !cmd.transport(); } bool K3bDevice::Device::setAutoEjectEnabled( bool enabled ) const { bool success = false; #ifdef Q_OS_LINUX bool needToClose = !isOpen(); usageLock(); if ( open() ) { success = ( ::ioctl( d->deviceFd, CDROMEJECT_SW, enabled ? 1 : 0 ) == 0 ); if ( needToClose ) { close(); } } usageUnlock(); #endif return success; } void K3bDevice::Device::addDeviceNode( const TQString& n ) { if( !d->allNodes.contains( n ) ) d->allNodes.append( n ); } const TQStringList& K3bDevice::Device::deviceNodes() const { return d->allNodes; } K3bDevice::Device::Handle K3bDevice::Device::handle() const { #ifdef Q_OS_FREEBSD return d->cam; #else return d->deviceFd; #endif } bool K3bDevice::Device::open( bool write ) const { if( d->openedReadWrite != write ) close(); TQMutexLocker ml( &d->openCloseMutex ); d->openedReadWrite = write; #ifdef Q_OS_FREEBSD if( !d->cam ) { d->cam = cam_open_pass (m_passDevice.latin1(), O_RDWR,0 /* NULL */); k3bDebug() << "(K3bDevice::openDevice) open device " << m_passDevice << ((d->cam)?" succeeded.":" failed.") << endl; } return (d->cam != 0); #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) if( d->deviceFd == -1 ) d->deviceFd = openDevice( TQFile::encodeName(devicename()), write ); return ( d->deviceFd != -1 ); #endif } void K3bDevice::Device::close() const { TQMutexLocker ml( &d->openCloseMutex ); #ifdef Q_OS_FREEBSD if( d->cam ) { cam_close_device(d->cam); d->cam = 0; } #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) if( d->deviceFd != -1 ) { ::close( d->deviceFd ); d->deviceFd = -1; } #endif } bool K3bDevice::Device::isOpen() const { #ifdef Q_OS_FREEBSD return d->cam; #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) return ( d->deviceFd != -1 ); #endif } int K3bDevice::Device::supportedProfiles() const { return d->supportedProfiles; } int K3bDevice::Device::currentProfile() const { unsigned char profileBuf[8]; ::memset( profileBuf, 0, 8 ); ScsiCommand cmd( this ); cmd[0] = MMC_GET_CONFIGURATION; cmd[1] = 1; cmd[8] = 8; cmd[9] = 0; // Necessary to set the proper command length if( cmd.transport( TR_DIR_READ, profileBuf, 8 ) ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " GET_CONFIGURATION failed." << endl; return MEDIA_UNKNOWN; } else { short profile = from2Byte( &profileBuf[6] ); // // Plextor drives might not set a current profile // In that case we get the list of all current profiles // and simply use the first one in that list. // if( profile == 0x00 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " current profile 0. Checking current profile list instead." << endl; unsigned char* data; unsigned int len = 0; if( getFeature( &data, len, FEATURE_PROFILE_LIST ) ) { int featureLen( data[11] ); for( int j = 0; j < featureLen; j+=4 ) { // use the first current profile we encounter if( data[12+j+2] & 0x1 ) { profile = from2Byte( &data[12+j] ); break; } } delete[] data; } } switch (profile) { case 0x00: return MEDIA_NONE; case 0x08: return MEDIA_CD_ROM; case 0x09: return MEDIA_CD_R; case 0x0A: return MEDIA_CD_RW; case 0x10: return MEDIA_DVD_ROM; case 0x11: return MEDIA_DVD_R_SEQ; case 0x12: return MEDIA_DVD_RAM; case 0x13: return MEDIA_DVD_RW_OVWR; case 0x14: return MEDIA_DVD_RW_SEQ; case 0x15: return MEDIA_DVD_R_DL_SEQ; case 0x16: return MEDIA_DVD_R_DL_JUMP; case 0x1A: return MEDIA_DVD_PLUS_RW; case 0x1B: return MEDIA_DVD_PLUS_R; case 0x2B: return MEDIA_DVD_PLUS_R_DL; case 0x40: return MEDIA_BD_ROM; case 0x41: { if( featureCurrent( FEATURE_BD_PSEUDO_OVERWRITE ) == 1 ) return MEDIA_BD_R_SRM_POW; else return MEDIA_BD_R_SRM; } case 0x42: return MEDIA_BD_R_RRM; case 0x43: return MEDIA_BD_RE; case 0x50: return MEDIA_HD_DVD_ROM; case 0x51: return MEDIA_HD_DVD_R; case 0x52: return MEDIA_HD_DVD_RAM; default: return MEDIA_UNKNOWN; } } } K3bDevice::DiskInfo K3bDevice::Device::diskInfo() const { DiskInfo inf; // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( open() ) { unsigned char* data = 0; unsigned int dataLen = 0; // // The first thing to do should be: checking if a media is loaded // We cannot rely on the profile here since at least some Plextor // drives return the NO MEDIUM profile for CD media // if( !testUnitReady() ) { // no disk or tray open inf.m_diskState = STATE_NO_MEDIA; inf.m_mediaType = MEDIA_NONE; inf.m_currentProfile = MEDIA_NONE; } else inf.m_currentProfile = currentProfile(); if( inf.diskState() != STATE_NO_MEDIA ) { if( readDiscInformation( &data, dataLen ) ) { disc_info_t* dInf = (disc_info_t*)data; // // Copy the needed values from the disk_info struct // switch( dInf->status ) { case 0: inf.m_diskState = STATE_EMPTY; break; case 1: inf.m_diskState = STATE_INCOMPLETE; break; case 2: inf.m_diskState = STATE_COMPLETE; break; default: inf.m_diskState = STATE_UNKNOWN; break; } switch( dInf->border ) { case 0: inf.m_lastSessionState = STATE_EMPTY; break; case 1: inf.m_lastSessionState = STATE_INCOMPLETE; break; case 2: inf.m_lastSessionState = STATE_COMPLETE; break; default: inf.m_lastSessionState = STATE_UNKNOWN; break; } switch( dInf->bg_f_status&0x3 ) { case 0x0: inf.m_bgFormatState = BG_FORMAT_NONE; break; case 0x1: inf.m_bgFormatState = BG_FORMAT_INCOMPLETE; break; case 0x2: inf.m_bgFormatState = BG_FORMAT_IN_PROGRESS; break; case 0x3: inf.m_bgFormatState = BG_FORMAT_COMPLETE; break; } inf.m_numTracks = (dInf->last_track_l & 0xff) | (dInf->last_track_m<<8 & 0xff00); if( inf.diskState() == STATE_EMPTY ) inf.m_numTracks = 0; // FIXME: I am not sure if this is accurate. Better test the last track's RT field else if( inf.diskState() == STATE_INCOMPLETE ) inf.m_numTracks--; // do not count the invisible track inf.m_rewritable = dInf->erasable; // // This is the Last Possible Lead-Out Start Address in HMSF format // This is only valid for CD-R(W) and DVD+R media. // For complete media this shall be filled with 0xff // if( dInf->lead_out_m != 0xff && dInf->lead_out_r != 0xff && dInf->lead_out_s != 0xff && dInf->lead_out_f != 0xff ) inf.m_capacity = K3b::Msf( dInf->lead_out_m + dInf->lead_out_r*60, dInf->lead_out_s, dInf->lead_out_f ) - 150; // // This is the position where the next Session shall be recorded in HMSF format // This is only valid for CD-R(W) and DVD+R media. // For complete media this shall be filled with 0xff // if( dInf->lead_in_m != 0xff && dInf->lead_in_r != 0xff && dInf->lead_in_s != 0xff && dInf->lead_in_f != 0xff ) inf.m_usedCapacity = K3b::Msf( dInf->lead_in_m + dInf->lead_in_r*60, dInf->lead_in_s, dInf->lead_in_f ) - 4500; delete [] data; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " fabricating disk information for a stupid device." << endl; Toc toc = readToc(); if( !toc.isEmpty() ) { inf.m_diskState = STATE_COMPLETE; inf.m_lastSessionState = STATE_COMPLETE; inf.m_numTracks = toc.count(); inf.m_capacity = inf.m_usedCapacity = toc.length(); } } // // The mediatype needs to be set // inf.m_mediaType = mediaType(); // At least some Plextor drives return profile NONE for CD media // or CD_ROM for writable media if( inf.m_mediaType & (MEDIA_UNKNOWN|MEDIA_NONE|MEDIA_CD_ROM) ) { // probably it is a CD if( inf.rewritable() ) inf.m_mediaType = MEDIA_CD_RW; else if( inf.empty() || inf.appendable() ) inf.m_mediaType = MEDIA_CD_R; else inf.m_mediaType = MEDIA_CD_ROM; } if( inf.m_mediaType & MEDIA_DVD_ALL ) { if( readDvdStructure( &data, dataLen ) ) { // some debugging stuff K3b::Msf sda, eda, ea0; sda = ( data[4+5]<<16 | data[4+6] << 8 | data[4+7] ); eda = ( data[4+9]<<16 | data[4+10] << 8 | data[4+11] ); ea0 = ( data[4+13]<<16 | data[4+14] << 8 | data[4+15] ); k3bDebug() << "First sec data area: " << sda.toString() << " (LBA " << TQString::number(sda.lba()) << ") (" << TQString::number(sda.mode1Bytes()) << endl; k3bDebug() << "Last sec data area: " << eda.toString() << " (LBA " << TQString::number(eda.lba()) << ") (" << TQString::number(eda.mode1Bytes()) << " Bytes)" << endl; k3bDebug() << "Last sec layer 1: " << ea0.toString() << " (LBA " << TQString::number(ea0.lba()) << ") (" << TQString::number(ea0.mode1Bytes()) << " Bytes)" << endl; K3b::Msf da0 = ea0 - sda + 1; K3b::Msf da1 = eda - ea0; k3bDebug() << "Layer 1 length: " << da0.toString() << " (LBA " << TQString::number(da0.lba()) << ") (" << TQString::number(da0.mode1Bytes()) << " Bytes)" << endl; k3bDebug() << "Layer 2 length: " << da1.toString() << " (LBA " << TQString::number(da1.lba()) << ") (" << TQString::number(da1.mode1Bytes()) << " Bytes)" << endl; inf.m_numLayers = ((data[6]&0x60) == 0 ? 1 : 2); bool otp = (data[4+2] & 0xF); // ea0 is 0 if the medium does not use Opposite track path if( otp && ea0 > 0 ) inf.m_firstLayerSize = da0; else inf.m_firstLayerSize = 0; delete [] data; } else { k3bDebug() << "(K3bDevice::Device) Unable to read DVD structure for num of layers." << endl; inf.m_numLayers = ( (inf.m_mediaType & MEDIA_WRITABLE_DVD_DL) ? 2 : 1 ); } } // // Number of sessions for non-empty disks // if( inf.diskState() != STATE_EMPTY ) { int sessions = numSessions(); if( sessions >= 0 ) inf.m_numSessions = sessions; else k3bDebug() << "(K3bDevice::Device) could not get session info via READ TOC/PMA/ATIP." << endl; } else inf.m_numSessions = 0; inf.m_mediaId = mediaId( inf.mediaType() ); // // Now we determine the size: // for all empty and appendable media READ FORMAT CAPACITIES should return the proper unformatted size // for complete disks we may use the READ_CAPACITY command or the start sector from the leadout // int media = inf.mediaType(); // // Use the profile if available because DVD-ROM units need to treat DVD+-R(W) media as DVD-ROM // if supported at all // if( inf.currentProfile() == MEDIA_DVD_ROM ) media = MEDIA_DVD_ROM; switch( media ) { case MEDIA_CD_R: case MEDIA_CD_RW: if( inf.m_capacity == 0 ) { if( readTocPmaAtip( &data, dataLen, 0x4, true, 0 ) ) { struct atip_descriptor* atip = (struct atip_descriptor*)data; if( dataLen >= 11 ) { inf.m_capacity = K3b::Msf( atip->lead_out_m, atip->lead_out_s, atip->lead_out_f ) - 150; debugBitfield( &atip->lead_out_m, 3 ); k3bDebug() << blockDeviceName() << ": ATIP capacity: " << inf.m_capacity.toString() << endl; } delete [] data; } } // // for empty and appendable media capacity and usedCapacity should be filled in from // diskinfo above. If not they are both still 0 // if( inf.m_capacity != 0 && ( inf.diskState() == STATE_EMPTY || inf.m_usedCapacity != 0 ) ) { // done. break; } default: case MEDIA_CD_ROM: if( inf.m_capacity > 0 && inf.m_usedCapacity == 0 ) inf.m_usedCapacity = inf.m_capacity; if( inf.m_usedCapacity == 0 ) { K3b::Msf readCap; if( readCapacity( readCap ) ) { k3bDebug() << "(K3bDevice::Device) READ CAPACITY: " << readCap.toString() << " other capacity: " << inf.m_capacity.toString() << endl; // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.m_usedCapacity = readCap + 1; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " Falling back to readToc for capacity." << endl; inf.m_usedCapacity = readToc().length(); } } case MEDIA_DVD_ROM: { K3b::Msf readCap; if( readCapacity( readCap ) ) { k3bDebug() << "(K3bDevice::Device) READ CAPACITY: " << readCap.toString() << " other capacity: " << inf.m_capacity.toString() << endl; // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.m_usedCapacity = readCap + 1; } else { // // Only one track, use it's size // if( readTrackInformation( &data, dataLen, 0x1, 0x1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.m_usedCapacity = from4Byte( trackInfo->track_size ); delete [] data; } else k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << "READ TRACK INFORMATION for DVD-ROM failed." << endl; } break; } case MEDIA_DVD_PLUS_R: case MEDIA_DVD_PLUS_R_DL: if( inf.appendable() || inf.empty() ) { // // get remaining space via the invisible track // if( readTrackInformation( &data, dataLen, 0x1, /*0xff*/ inf.numTracks()+1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.m_usedCapacity = from4Byte( trackInfo->track_start ); inf.m_capacity = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size ); delete [] data; } } else { if( readTrackInformation( &data, dataLen, 0x1, inf.numTracks() ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.m_capacity = inf.m_usedCapacity = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size ); delete [] data; } } break; case MEDIA_DVD_R: case MEDIA_DVD_R_SEQ: case MEDIA_DVD_R_DL: case MEDIA_DVD_R_DL_JUMP: case MEDIA_DVD_R_DL_SEQ: // // get data from the incomplete track (which is NOT the invisible track 0xff) // This will fail in case the media is complete! // if( readTrackInformation( &data, dataLen, 0x1, inf.numTracks()+1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.m_usedCapacity = from4Byte( trackInfo->track_start ); inf.m_capacity = from4Byte( trackInfo->free_blocks ) + from4Byte( trackInfo->track_start ); delete [] data; } // // Get the "really" used space without border-out // if( !inf.empty() ) { K3b::Msf readCap; if( readCapacity( readCap ) ) { // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.m_usedCapacity = readCap + 1; } else k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " READ CAPACITY for DVD-R failed." << endl; } break; case MEDIA_DVD_RW_OVWR: inf.m_numSessions = 1; case MEDIA_DVD_RW: case MEDIA_DVD_RW_SEQ: // only one track on a DVD-RW media if( readTrackInformation( &data, dataLen, 0x1, 0x1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.m_capacity = from4Byte( trackInfo->track_size ); if( !inf.empty() ) { if( readFormatCapacity( 0x10, inf.m_capacity ) ) k3bDebug() << blockDeviceName() << ": Format capacity 0x10: " << inf.m_capacity.toString() << endl; inf.m_usedCapacity = from4Byte( trackInfo->track_size ); } delete [] data; } break; case MEDIA_DVD_PLUS_RW: { K3b::Msf currentMax; int currentMaxFormat = 0; if( readFormatCapacity( 0x26, inf.m_capacity, ¤tMax, ¤tMaxFormat ) ) { if( currentMaxFormat == 0x1 ) { // unformatted or blank media inf.m_usedCapacity = 0; inf.m_capacity = currentMax; } else { inf.m_usedCapacity = currentMax; // Plextor drives tend to screw things up and report invalid values // for the max format capacity of 1.4 GB DVD media if ( inf.bgFormatState() == BG_FORMAT_COMPLETE ) { inf.m_capacity = currentMax; } } } else k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " READ FORMAT CAPACITIES for DVD+RW failed." << endl; break; } case MEDIA_BD_R: case MEDIA_BD_R_SRM: case MEDIA_BD_R_SRM_POW: case MEDIA_BD_R_RRM: case MEDIA_BD_RE: // // get the invisible track's first sector // or the next writable address of the last open track // if( readDiscInformation( &data, dataLen ) ) { int lastTrack = (int)( data[11]<<8 | data[6] ); delete [] data; if( readTrackInformation( &data, dataLen, 1, lastTrack ) ) { // capacity: last track's start address + last track's size inf.m_capacity = from4Byte( data+8 ) + from4Byte( data+24 ); if( data[6] & 0x80 ) inf.m_usedCapacity = from4Byte( data+8 ); else if( data[7] & 0x1 ) inf.m_usedCapacity = from4Byte( data+12 ); delete [] data; } } break; case MEDIA_BD_ROM: { K3b::Msf readCap; if( readCapacity( readCap ) ) { // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.m_usedCapacity = readCap + 1; } break; } } } if( needToClose ) close(); } return inf; } int K3bDevice::Device::mediaType() const { int m = MEDIA_UNKNOWN; if( testUnitReady() ) { m = currentProfile(); if( m & (MEDIA_UNKNOWN|MEDIA_DVD_ROM|MEDIA_HD_DVD_ROM) ) { // // We prefere the mediatype as reported by the media since this way // even ROM drives may report the correct type of writable media. // // 4 bytes header + 2048 bytes layer descriptor unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen ) ) { switch( data[4]&0xF0 ) { case 0x00: m = MEDIA_DVD_ROM; break; case 0x10: m = MEDIA_DVD_RAM; break; case 0x20: m = MEDIA_DVD_R; break; // there seems to be no value for DVD-R DL, it reports DVD-R case 0x30: m = MEDIA_DVD_RW; break; case 0x40: m = MEDIA_HD_DVD_ROM; break; case 0x50: m = MEDIA_HD_DVD_R; break; case 0x60: m = MEDIA_HD_DVD_RAM; break; case 0x90: m = MEDIA_DVD_PLUS_RW; break; case 0xA0: m = MEDIA_DVD_PLUS_R; break; case 0xE0: m = MEDIA_DVD_PLUS_R_DL; break; default: k3bDebug() << "(K3bDevice::Device) unknown dvd media type: " << TQString::number(data[4]&0xF0, 8) << endl; break; // unknown } delete [] data; } } if( m & (MEDIA_UNKNOWN|MEDIA_BD_ROM) ) { // // We prefere the mediatype as reported by the media since this way // even ROM drives may report the correct type of writable media. // unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscStructure( &data, dataLen, 1, 0 ) ) { if( dataLen > 4+12 && data[4+8] == 'B' && data[4+9] == 'D' ) { switch( data[4+10] ) { case 'O': m = MEDIA_BD_ROM; break; case 'W': m = MEDIA_BD_RE; break; case 'R': m = MEDIA_BD_R; break; } } delete [] data; } } // // Only old CD or DVD devices do not report a current profile // or report CD-ROM profile for all CD types // if( m & (MEDIA_UNKNOWN|MEDIA_CD_ROM) ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 4, false, 0 ) ) { if( (data[6]>>6)&1 ) m = MEDIA_CD_RW; else m = MEDIA_CD_R; delete [] data; } else m = MEDIA_CD_ROM; } } return m; } bool K3bDevice::Device::readSectorsRaw( unsigned char *buf, int start, int count ) const { return readCd( buf, count*2352, 0, // all sector types false, // no dap start, count, true, // SYNC true, // HEADER true, // SUBHEADER true, // USER DATA true, // EDC/ECC 0, // no c2 info 0 ); } void K3bDevice::Device::checkForJustLink() { unsigned char* ricoh = 0; unsigned int ricohLen = 0; if( modeSense( &ricoh, ricohLen, 0x30 ) ) { // // 8 byte mode header + 6 byte page data // if( ricohLen >= 14 ) { ricoh_mode_page_30* rp = (ricoh_mode_page_30*)(ricoh+8); d->burnfree = rp->BUEFS; } delete [] ricoh; } } void K3bDevice::Device::checkFeatures() { unsigned char header[1024]; ::memset( header, 0, 1024 ); ScsiCommand cmd( this ); cmd[0] = MMC_GET_CONFIGURATION; cmd[1] = 2; cmd[9] = 0; // Necessary to set the proper command length // // CD writing features // cmd[2] = FEATURE_CD_MASTERING>>8; cmd[3] = FEATURE_CD_MASTERING; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "CD Mastering" << endl; #ifdef WORDS_BIGENDIAN struct cd_mastering_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char SAO : 1; // Session At Once writing unsigned char raw_ms : 1; // Writing Multisession in Raw Writing Mode unsigned char raw : 1; // Writing in WRITINGMODE_RAW mode unsigned char testwrite : 1; // Simulation write support unsigned char cd_rw : 1; // CD-RW support unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char max_cue_length[3]; }; #else struct cd_mastering_feature { unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char cd_rw : 1; // CD-RW support unsigned char testwrite : 1; // Simulation write support unsigned char raw : 1; // Writing in WRITINGMODE_RAW mode unsigned char raw_ms : 1; // Writing Multisession in Raw Writing Mode unsigned char SAO : 1; // Session At Once writing unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char max_cue_length[3]; }; #endif struct cd_mastering_feature* p = (struct cd_mastering_feature*)&header[12]; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= MEDIA_CD_R; if( p->cd_rw ) d->writeCapabilities |= MEDIA_CD_RW; // if( p->WRITINGMODE_SAO ) m_writeModes |= WRITINGMODE_SAO; // if( p->raw || p->raw_ms ) m_writeModes |= WRITINGMODE_RAW; // WRITINGMODE_RAW16 always supported when raw is supported? } } cmd[2] = FEATURE_CD_TRACK_AT_ONCE>>8; cmd[3] = FEATURE_CD_TRACK_AT_ONCE; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "CD Track At Once" << endl; #ifdef WORDS_BIGENDIAN struct cd_track_at_once_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved2 : 1; unsigned char rw_raw : 1; // Writing R-W subcode in Raw mode unsigned char rw_pack : 1; // Writing R-W subcode in Packet mode unsigned char testwrite : 1; // Simulation write support unsigned char cd_rw : 1; // CD-RW support unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char reserved3; unsigned char data_type[2]; }; #else struct cd_track_at_once_feature { unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char cd_rw : 1; // CD-RW support unsigned char testwrite : 1; // Simulation write support unsigned char rw_pack : 1; // Writing R-W subcode in Packet mode unsigned char rw_raw : 1; // Writing R-W subcode in Raw mode unsigned char reserved2 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char reserved3; unsigned char data_type[2]; }; #endif struct cd_track_at_once_feature* p = (struct cd_track_at_once_feature*)&header[12]; m_writeModes |= WRITINGMODE_TAO; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= MEDIA_CD_R; if( p->cd_rw ) d->writeCapabilities |= MEDIA_CD_RW; // is the following correct? What exactly does rw_sub tell us? // if( m_writeModes & WRITINGMODE_RAW ) { // if( p->rw_raw ) m_writeModes |= WRITINGMODE_RAW_R96R; // if( p->rw_pack ) m_writeModes |= WRITINGMODE_RAW_R96P; // } // // check the data types for 1, 2, and 3 (raw16, raw96p, and raw96r) // debugBitfield( p->data_type, 2 ); // if( m_writeModes & WRITINGMODE_RAW ) { // if( p->data_type[1] & 0x20 ) m_writeModes |= WRITINGMODE_RAW_R16; // if( p->data_type[1] & 0x40 ) m_writeModes |= WRITINGMODE_RAW_R96P; // if( p->data_type[1] & 0x80 ) m_writeModes |= WRITINGMODE_RAW_R96R; // } } } cmd[2] = FEATURE_CD_RW_MEDIA_WRITE_SUPPORT>>8; cmd[3] = FEATURE_CD_RW_MEDIA_WRITE_SUPPORT; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "CD-RW Media Write Support" << endl; d->writeCapabilities |= (MEDIA_CD_R|MEDIA_CD_RW); } } // // DVD-ROM // // FIXME: since MMC5 the feature descr. is 8 bytes in length including a dvd dl read bit at byte 6 cmd[2] = FEATURE_DVD_READ>>8; cmd[3] = FEATURE_DVD_READ; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD Read (MMC5)" << endl; d->readCapabilities |= MEDIA_DVD_ROM; if( header[8+6] & 0x1 ) d->readCapabilities |= MEDIA_WRITABLE_DVD_DL; } } else { // retry with pre-MMC5 length cmd[8] = 8+4; if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { unsigned int len = from4Byte( header ); if( len >= 8 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD Read (pre-MMC5)" << endl; d->readCapabilities |= MEDIA_DVD_ROM; } } } // // DVD+R(W) writing features // cmd[2] = FEATURE_DVD_PLUS_R>>8; cmd[3] = FEATURE_DVD_PLUS_R; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD+R" << endl; d->readCapabilities |= MEDIA_DVD_PLUS_R; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_R; } } cmd[2] = FEATURE_DVD_PLUS_RW>>8; cmd[3] = FEATURE_DVD_PLUS_RW; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD+RW" << endl; #ifdef WORDS_BIGENDIAN struct dvd_plus_rw_feature { unsigned char reserved1 : 7; unsigned char write : 1; unsigned char reserved2 : 6; unsigned char quick_start : 1; unsigned char close_only : 1; // and some stuff we do not use here... }; #else struct dvd_plus_rw_feature { unsigned char write : 1; unsigned char reserved1 : 7; unsigned char close_only : 1; unsigned char quick_start : 1; unsigned char reserved2 : 6; // and some stuff we do not use here... }; #endif struct dvd_plus_rw_feature* p = (struct dvd_plus_rw_feature*)&header[12]; d->readCapabilities |= MEDIA_DVD_PLUS_RW; if( p->write ) d->writeCapabilities |= MEDIA_DVD_PLUS_RW; } } // some older DVD-ROM drives claim to support DVD+R DL if( d->writeCapabilities & MEDIA_DVD_PLUS_R ) { cmd[2] = FEATURE_DVD_PLUS_RW_DUAL_LAYER>>8; cmd[3] = FEATURE_DVD_PLUS_RW_DUAL_LAYER; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD+RW Double Layer" << endl; d->readCapabilities |= MEDIA_DVD_PLUS_RW_DL; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_RW_DL; } } cmd[2] = FEATURE_DVD_PLUS_R_DUAL_LAYER>>8; cmd[3] = FEATURE_DVD_PLUS_R_DUAL_LAYER; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD+R Double Layer" << endl; d->readCapabilities |= MEDIA_DVD_PLUS_R_DL; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_R_DL; } } } // // Blue Ray // // We do not care for the different BD classes and versions here // cmd[2] = FEATURE_BD_READ>>8; cmd[3] = FEATURE_BD_READ; cmd[8] = 8+32; if( !cmd.transport( TR_DIR_READ, header, 40 ) ) { unsigned int len = from4Byte( header ); if( len >= 36 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "BD Read" << endl; if( header[8+8] || header[8+9] || header[8+10] || header[8+11] || header[8+12] || header[8+13] || header[8+14] || header[8+15] ) d->readCapabilities |= MEDIA_BD_RE; if( header[8+16] || header[8+17] || header[8+18] || header[8+19] || header[8+20] || header[8+21] || header[8+22] || header[8+23] ) d->readCapabilities |= MEDIA_BD_R; if( header[8+24] || header[8+25] || header[8+26] || header[8+27] || header[8+28] || header[8+29] || header[8+30] || header[8+31] ) d->readCapabilities |= MEDIA_BD_ROM; } } cmd[2] = FEATURE_BD_WRITE>>8; cmd[3] = FEATURE_BD_WRITE; cmd[8] = 8+24; if( !cmd.transport( TR_DIR_READ, header, 32 ) ) { unsigned int len = from4Byte( header ); if( len >= 28 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "BD Write" << endl; if( header[8+8] || header[8+9] || header[8+10] || header[8+11] || header[8+12] || header[8+13] || header[8+14] || header[8+15] ) d->writeCapabilities |= MEDIA_BD_RE; if( header[8+16] || header[8+17] || header[8+18] || header[8+19] || header[8+20] || header[8+21] || header[8+22] || header[8+23] ) { d->writeCapabilities |= MEDIA_BD_R; m_writeModes |= WRITINGMODE_SRM; cmd[2] = FEATURE_BD_PSEUDO_OVERWRITE>>8; cmd[3] = FEATURE_BD_PSEUDO_OVERWRITE; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 8+8 ) ) { unsigned int len = from4Byte( header ); if( len >= 4+8 ) { m_writeModes |= WRITINGMODE_SRM_POW; } } cmd[2] = FEATURE_RANDOM_WRITABLE>>8; cmd[3] = FEATURE_RANDOM_WRITABLE; cmd[8] = 8+16; if( !cmd.transport( TR_DIR_READ, header, 8+16 ) ) { unsigned int len = from4Byte( header ); if( len >= 4+16 ) { m_writeModes |= WRITINGMODE_RRM; } } } } } // // DVD-R(W) // cmd[2] = FEATURE_DVD_R_RW_WRITE>>8; cmd[3] = FEATURE_DVD_R_RW_WRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "DVD-R/-RW Write" << endl; #ifdef WORDS_BIGENDIAN struct dvd_r_rw_write_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved2 : 2; unsigned char RDL : 1; unsigned char testwrite : 1; // Simulation write support unsigned char dvd_rw : 1; // DVD-RW Writing unsigned char reserved3 : 1; unsigned char reserved4[3]; }; #else struct dvd_r_rw_write_feature { unsigned char reserved3 : 1; unsigned char dvd_rw : 1; // DVD-RW Writing unsigned char testwrite : 1; // Simulation write support unsigned char RDL : 1; unsigned char reserved2 : 2; unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char reserved4[3]; }; #endif struct dvd_r_rw_write_feature* p = (struct dvd_r_rw_write_feature*)&header[12]; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_SEQ); if( p->dvd_rw ) d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_SEQ); if( p->RDL ) d->writeCapabilities |= (MEDIA_DVD_R_DL|MEDIA_DVD_R_DL_SEQ); m_dvdMinusTestwrite = p->testwrite; } } // // DVD-RW restricted overwrite check // cmd[2] = FEATURE_RIGID_RESTRICTED_OVERWRITE>>8; cmd[3] = FEATURE_RIGID_RESTRICTED_OVERWRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "Rigid Restricted Overwrite" << endl; m_writeModes |= WRITINGMODE_RES_OVWR; d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_OVWR); } } // // DVD-R Dual Layer Layer // cmd[2] = FEATURE_LAYER_JUMP_RECORDING>>8; cmd[3] = FEATURE_LAYER_JUMP_RECORDING; cmd[8] = 12; if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { // Now the jump feature is longer than 4 bytes but we don't need the link sizes. unsigned int len = from4Byte( header ); if( len >= 8 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "Layer Jump Recording" << endl; d->writeCapabilities |= (MEDIA_DVD_R_DL|MEDIA_DVD_R_DL_JUMP); m_writeModes |= WRITINGMODE_LAYER_JUMP; } } // // HD-DVD-ROM // cmd[2] = FEATURE_HD_DVD_READ>>8; cmd[3] = FEATURE_HD_DVD_READ; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "HD-DVD Read" << endl; d->readCapabilities |= MEDIA_HD_DVD_ROM; if( header[8+4] & 0x1 ) d->readCapabilities |= MEDIA_HD_DVD_R; if( header[8+6] & 0x1 ) d->readCapabilities |= MEDIA_HD_DVD_RAM; } } // // HD-DVD-R(AM) // cmd[2] = FEATURE_HD_DVD_WRITE>>8; cmd[3] = FEATURE_HD_DVD_WRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " feature: " << "HD-DVD Write" << endl; if( header[8+4] & 0x1 ) d->writeCapabilities |= MEDIA_HD_DVD_R; if( header[8+6] & 0x1 ) d->writeCapabilities |= MEDIA_HD_DVD_RAM; } } // // Get the profiles // // the max len of the returned data is 8 (header) + 4 (feature) + 255 (additional length) // cmd[2] = FEATURE_PROFILE_LIST>>8; cmd[3] = FEATURE_PROFILE_LIST; cmd[8] = 12; // get the number of returned profiles first if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { unsigned int len = from4Byte( header ) + 4; if( len >= 12 ) { cmd[7] = len>>8; cmd[8] = len; if( !cmd.transport( TR_DIR_READ, header, len ) ) { int featureLen( header[11] ); for( int j = 0; j < featureLen; j+=4 ) { short profile = from2Byte( &header[12+j] ); switch (profile) { case 0x08: d->supportedProfiles |= MEDIA_CD_ROM; break; case 0x09: d->supportedProfiles |= MEDIA_CD_R; break; case 0x0A: d->supportedProfiles |= MEDIA_CD_RW; break; case 0x10: d->supportedProfiles |= MEDIA_DVD_ROM; // d->readCapabilities |= MEDIA_DVD_ROM; break; case 0x11: d->supportedProfiles |= MEDIA_DVD_R_SEQ; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_SEQ); break; case 0x12: d->supportedProfiles |= MEDIA_DVD_RAM; // d->readCapabilities |= (MEDIA_DVD_RAM|MEDIA_DVD_ROM); // d->writeCapabilities |= MEDIA_DVD_RAM; break; case 0x13: d->supportedProfiles |= MEDIA_DVD_RW_OVWR; // d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_OVWR); break; case 0x14: d->supportedProfiles |= MEDIA_DVD_RW_SEQ; // d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_R|MEDIA_DVD_RW_SEQ|MEDIA_DVD_R_SEQ); break; case 0x15: d->supportedProfiles |= MEDIA_DVD_R_DL_SEQ; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_DL|MEDIA_DVD_R_SEQ|MEDIA_DVD_R_DL_SEQ); break; case 0x16: d->supportedProfiles |= MEDIA_DVD_R_DL_JUMP; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_DL||MEDIA_DVD_R_DL_JUMP); break; case 0x1A: d->supportedProfiles |= MEDIA_DVD_PLUS_RW; // d->writeCapabilities |= MEDIA_DVD_PLUS_RW; break; case 0x1B: d->supportedProfiles |= MEDIA_DVD_PLUS_R; // d->writeCapabilities |= MEDIA_DVD_PLUS_R; break; case 0x2A: d->supportedProfiles |= MEDIA_DVD_PLUS_RW_DL; // d->writeCapabilities |= MEDIA_DVD_PLUS_RW_DL; break; case 0x2B: d->supportedProfiles |= MEDIA_DVD_PLUS_R_DL; // d->writeCapabilities |= MEDIA_DVD_PLUS_R_DL; break; case 0x40: d->supportedProfiles |= MEDIA_BD_ROM; break; case 0x41: d->supportedProfiles |= MEDIA_BD_R_SRM; break; case 0x42: d->supportedProfiles |= MEDIA_BD_R_RRM; break; case 0x43: d->supportedProfiles |= MEDIA_BD_RE; break; case 0x50: d->supportedProfiles |= MEDIA_HD_DVD_ROM; break; case 0x51: d->supportedProfiles |= MEDIA_HD_DVD_R; break; case 0x52: d->supportedProfiles |= MEDIA_HD_DVD_RAM; break; default: k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " unknown profile: " << profile << endl; } } // some older DVD-ROM drives claim to support DVD+R DL if( !(d->supportedProfiles & MEDIA_DVD_PLUS_R) ) { // remove DVD+R DL capability // d->writeCapabilities &= ~MEDIA_DVD_PLUS_R_DL; d->supportedProfiles &= ~MEDIA_DVD_PLUS_R_DL; } } } } } void K3bDevice::Device::checkFor2AFeatures() { unsigned char* mm_cap_buffer = 0; unsigned int mm_cap_len = 0; if( modeSense( &mm_cap_buffer, mm_cap_len, 0x2A ) ) { mm_cap_page_2A* mm_p = (mm_cap_page_2A*)(mm_cap_buffer+8); if( mm_p->BUF ) d->burnfree = true; if( mm_p->cd_r_write ) d->writeCapabilities |= MEDIA_CD_R; else d->writeCapabilities &= ~MEDIA_CD_R; if( mm_p->cd_rw_write ) d->writeCapabilities |= MEDIA_CD_RW; else d->writeCapabilities &= ~MEDIA_CD_RW; if( mm_p->dvd_r_write ) d->writeCapabilities |= MEDIA_DVD_R; else d->writeCapabilities &= ~MEDIA_DVD_R; if( mm_p->dvd_rom_read || mm_p->dvd_r_read ) d->readCapabilities |= MEDIA_DVD_ROM; m_maxReadSpeed = from2Byte(mm_p->max_read_speed); m_bufferSize = from2Byte( mm_p->buffer_size ); delete [] mm_cap_buffer; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": read mode page 2A failed!" << endl; } } void K3bDevice::Device::checkWritingModes() { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return; // header size is 8 unsigned char* buffer = 0; unsigned int dataLen = 0; if( !modeSense( &buffer, dataLen, 0x05 ) ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": modeSense 0x05 failed!" << endl << "(K3bDevice::Device) " << blockDeviceName() << ": Cannot check write modes." << endl; } else if( dataLen < 18 ) { // 8 bytes header + 10 bytes used modepage k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": Missing modepage 0x05 data." << endl << "(K3bDevice::Device) " << blockDeviceName() << ": Cannot check write modes." << endl; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": dataLen: " << dataLen << endl; wr_param_page_05* mp = (struct wr_param_page_05*)(buffer+8); // reset some stuff to be on the safe side mp->PS = 0; mp->BUFE = 0; mp->multi_session = 0; mp->test_write = 0; mp->LS_V = 0; mp->copy = 0; mp->fp = 0; mp->host_appl_code= 0; mp->session_format = 0; mp->audio_pause_len[0] = 0; mp->audio_pause_len[1] = 150; // WRITINGMODE_TAO mp->write_type = 0x01; // Track-at-once mp->track_mode = 4; // MMC-4 says: 5, cdrecord uses 4 ? mp->dbtype = 8; // Mode 1 // k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": modeselect WRITINGMODE_TAO data: " << endl; // debugBitfield( buffer, dataLen ); // // if a writer does not support WRITINGMODE_TAO it surely does not support WRITINGMODE_SAO or WRITINGMODE_RAW writing since WRITINGMODE_TAO is the minimal // requirement // k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for TAO" << endl; if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_TAO; d->writeCapabilities |= MEDIA_CD_R; // WRITINGMODE_SAO mp->write_type = 0x02; // Session-at-once k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for SAO" << endl; if( modeSelect( buffer, dataLen, 1, 0 ) ) m_writeModes |= WRITINGMODE_SAO; // mp->dbtype = 1; // Raw data with P and Q Sub-channel (2368 bytes) // if( modeSelect( buffer, dataLen, 1, 0 ) ) { // m_writeModes |= WRITINGMODE_RAW_R16; // } k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for SAO_R96P" << endl; mp->dbtype = 2; // Raw data with P-W Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_SAO_R96P; } k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for SAO_R96R" << endl; mp->dbtype = 3; // Raw data with P-W raw Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_SAO_R96R; } k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for RAW_R16" << endl; // WRITINGMODE_RAW mp->write_type = 0x03; // WRITINGMODE_RAW mp->dbtype = 1; // Raw data with P and Q Sub-channel (2368 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_RAW; m_writeModes |= WRITINGMODE_RAW_R16; } k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for RAW_R96P" << endl; mp->dbtype = 2; // Raw data with P-W Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_RAW; m_writeModes |= WRITINGMODE_RAW_R96P; } k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": checking for RAW_R96R" << endl; mp->dbtype = 3; // Raw data with P-W raw Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { m_writeModes |= WRITINGMODE_RAW; m_writeModes |= WRITINGMODE_RAW_R96R; } } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": modeSelect with WRITINGMODE_TAO failed. No writer" << endl; } delete [] buffer; } if( needToClose ) close(); } int K3bDevice::Device::determineMaximalWriteSpeed() const { int ret = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( mediaType() & MEDIA_CD_ALL ) { if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; // MMC1 used byte 18 and 19 for the max write speed if( dataLen > 19 ) ret = from2Byte( mm->max_write_speed ); delete [] data; if( ret > 0 ) return ret; } } TQValueList list = determineSupportedWriteSpeeds(); if( !list.isEmpty() ) { for( TQValueList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) ret = TQMAX( ret, *it ); } if( ret > 0 ) return ret; else return m_maxWriteSpeed; } TQValueList K3bDevice::Device::determineSupportedWriteSpeeds() const { TQValueList ret; if( burner() ) { // // Tests with all my drives resulted in 2A for CD and GET PERFORMANCE for DVD media // as the valid method of speed detection. // if( mediaType() & MEDIA_CD_ALL ) { if( !getSupportedWriteSpeedsVia2A( ret, false ) ) getSupportedWriteSpeedsViaGP( ret, false ); // restrict to max speed, although deprecated in MMC3 is still used everywhere and // cdrecord also uses it as the max writing speed. int max = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; // MMC1 used byte 18 and 19 for the max write speed if( dataLen > 19 ) max = from2Byte( mm->max_write_speed ); delete [] data; if( max > 0 ) { while( !ret.isEmpty() && ret.last() > max ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " writing speed " << ret.last() << " higher than max " << max << endl; ret.pop_back(); } } } } else { if( !getSupportedWriteSpeedsViaGP( ret, true ) ) getSupportedWriteSpeedsVia2A( ret, true ); } } return ret; } bool K3bDevice::Device::getSupportedWriteSpeedsVia2A( TQValueList& list, bool dvd ) const { unsigned char* data = 0; unsigned int dataLen = 0; if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; if( dataLen > 32 ) { // we have descriptors unsigned int numDesc = from2Byte( mm->num_wr_speed_des ); // Some CDs writer returns the number of bytes that contain // the descriptors rather than the number of descriptors // Ensure number of descriptors claimed actually fits in the data // returned by the mode sense command. if( numDesc > ((dataLen - 32 - 8) / 4) ) numDesc = (dataLen - 32 - 8) / 4; cd_wr_speed_performance* wr = (cd_wr_speed_performance*)mm->wr_speed_des; k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": Number of supported write speeds via 2A: " << numDesc << endl; for( unsigned int i = 0; i < numDesc; ++i ) { int s = (int)from2Byte( wr[i].wr_speed_supp ); // // some DVD writers report CD writing speeds here // If that is the case we cannot rely on the reported speeds // and need to use the values gained from GET PERFORMANCE. // if( dvd && s < 1352 ) { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " Invalid DVD speed: " << s << " KB/s" << endl; list.clear(); break; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " : " << s << " KB/s" << endl; if( dvd ) s = fixupDvdWritingSpeed( s ); // sort the list TQValueList::iterator it = list.begin(); while( it != list.end() && *it < s ) ++it; list.insert( it, s ); } } } delete [] data; } return !list.isEmpty(); } bool K3bDevice::Device::getSupportedWriteSpeedsViaGP( TQValueList& list, bool dvd ) const { unsigned char* data = 0; unsigned int dataLen = 0; if( getPerformance( &data, dataLen, 0x3, 0x0 ) ) { int numDesc = (dataLen - 8)/16; k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << ": Number of supported write speeds via GET PERFORMANCE: " << numDesc << endl; for( int i = 0; i < numDesc; ++i ) { int s = from4Byte( &data[20+i*16] ); // Looks as if the code below does not make sense with most drives // if( !( data[4+i*16] & 0x2 ) ) { // k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() // << " No write speed: " << s << " KB/s" << endl; // continue; // } if( dvd && s < 1352 ) { // // Does this ever happen? // k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " Invalid DVD speed: " << s << " KB/s" << endl; } else { k3bDebug() << "(K3bDevice::Device) " << blockDeviceName() << " : " << s << " KB/s" << endl; if( dvd ) s = fixupDvdWritingSpeed( s ); TQValueList::iterator it = list.begin(); while( it != list.end() && *it < s ) ++it; // the speed might already have been found in the 2a modepage if( it == list.end() || *it != s ) list.insert( it, s ); } } delete [] data; } return !list.isEmpty(); } int K3bDevice::Device::getIndex( unsigned long lba ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return -1; int ret = -1; // // first try readCd // unsigned char readData[16]; ::memset( readData, 0, 16 ); // // The index is found in the Mode-1 Q which occupies at least 9 out of 10 successive CD frames // It can be indentified by ADR == 1 // // So if the current sector does not provide Mode-1 Q subchannel we try the previous. // if( readCd( readData, 16, 1, // CD-DA 0, // no DAP lba, 1, false, false, false, false, false, 0, 2 // Q-Subchannel ) ) { // byte 0: 4 bits CONTROL (MSB) + 4 bits ADR (LSB) if( (readData[0]&0x0f) == 0x1 ) ret = readData[2]; // search previous sector for Mode1 Q Subchannel else if( readCd( readData, 16, 1, // CD-DA 0, // no DAP lba-1, 1, false, false, false, false, false, 0, 2 // Q-Subchannel ) ) { if( (readData[0]&0x0f) == 0x1 ) ret = readData[2]; else ret = -2; } } else { k3bDebug() << "(K3bDevice::Device::getIndex) readCd failed. Trying seek." << endl; unsigned char* data = 0; unsigned int dataLen = 0; if( seek( lba ) && readSubChannel( &data, dataLen, 1, 0 ) ) { // byte 5: 4 bits ADR (MSB) + 4 bits CONTROL (LSB) if( dataLen > 7 && (data[5]>>4 & 0x0F) == 0x1 ) { ret = data[7]; } else if( seek( lba-1 ) && readSubChannel( &data, dataLen, 1, 0 ) ) { if( dataLen > 7 && (data[5]>>4 & 0x0F) == 0x1 ) ret = data[7]; else ret = -2; } delete [] data; } else k3bDebug() << "(K3bDevice::Device::getIndex) seek or readSubChannel failed." << endl; } if( needToClose ) close(); return ret; } bool K3bDevice::Device::searchIndex0( unsigned long startSec, unsigned long endSec, long& pregapStart ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return false; bool ret = false; int lastIndex = getIndex( endSec ); if( lastIndex == 0 ) { // there is a pregap // let's find the position where the index turns to 0 // we jump in 1 sec steps backwards until we find an index > 0 unsigned long sector = endSec; while( lastIndex == 0 && sector > startSec ) { sector -= 75; if( sector < startSec ) sector = startSec; lastIndex = getIndex(sector); } if( lastIndex == 0 ) { k3bDebug() << "(K3bDevice::Device) warning: no index != 0 found." << endl; } else { // search forward to the first index = 0 while( getIndex( sector ) != 0 && sector < endSec ) sector++; pregapStart = sector; ret = true; } } else if( lastIndex > 0 ) { // no pregap pregapStart = -1; ret = true; } if( needToClose ) close(); return ret; } bool K3bDevice::Device::indexScan( K3bDevice::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return false; bool ret = true; for( Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { Track& track = *it; if( track.type() == Track::AUDIO ) { track.m_indices.clear(); long index0 = -1; if( searchIndex0( track.firstSector().lba(), track.lastSector().lba(), index0 ) ) { k3bDebug() << "(K3bDevice::Device) found index 0: " << index0 << endl; } if( index0 > 0 ) track.m_index0 = K3b::Msf( index0 - track.firstSector().lba() ); else track.m_index0 = 0; if( index0 > 0 ) searchIndexTransitions( track.firstSector().lba(), index0-1, track ); else searchIndexTransitions( track.firstSector().lba(), track.lastSector().lba(), track ); } } if( needToClose ) close(); return ret; } void K3bDevice::Device::searchIndexTransitions( long start, long end, K3bDevice::Track& track ) const { k3bDebug() << "(K3bDevice::Device) searching for index transitions between " << start << " and " << end << endl; int startIndex = getIndex( start ); int endIndex = getIndex( end ); if( startIndex < 0 || endIndex < 0 ) { k3bDebug() << "(K3bDevice::Device) could not retrieve index values." << endl; } else { k3bDebug() << "(K3bDevice::Device) indices: " << start << " - " << startIndex << " and " << end << " - " << endIndex << endl; if( startIndex != endIndex ) { if( start+1 == end ) { k3bDebug() << "(K3bDevice::Device) found index transition: " << endIndex << " " << end << endl; track.m_indices.resize( endIndex ); // we save the index relative to the first sector track.m_indices[endIndex-1] = K3b::Msf( end ) - track.firstSector(); } else { searchIndexTransitions( start, start+(end-start)/2, track ); searchIndexTransitions( start+(end-start)/2, end, track ); } } } } int K3bDevice::Device::copyrightProtectionSystemType() const { unsigned char* dvdheader = 0; unsigned int dataLen = 0; if( readDvdStructure( &dvdheader, dataLen, 0x1 ) ) { int ret = -1; if( dataLen >= 6 ) ret = dvdheader[4]; delete [] dvdheader; return ret; } else return -1; } bool K3bDevice::Device::getNextWritableAdress( unsigned int& lastSessionStart, unsigned int& nextWritableAdress ) const { bool success = false; // FIXME: add CD media handling int m = mediaType(); if( m & MEDIA_DVD_ALL ) { // DVD+RW always returns complete if( m & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) return false; unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; // // The state of the last session has to be "empty" (0x0) or "incomplete" (0x1) // The procedure here is taken from the dvd+rw-tools // if( !(inf->border & 0x2) ) { // the incomplete track number is the first track in the last session (the empty session) int nextTrack = inf->first_track_l|inf->first_track_m<<8; unsigned char* trackData = 0; unsigned int trackDataLen = 0; // Read start address of the incomplete track if( readTrackInformation( &trackData, trackDataLen, 0x1, nextTrack ) ) { nextWritableAdress = from4Byte( &trackData[8] ); delete [] trackData; // Read start address of the first track in the last session if( readTocPmaAtip( &trackData, trackDataLen, 0x1, false, 0x0 ) ) { lastSessionStart = from4Byte( &trackData[8] ); delete [] trackData; success = true; } } } } delete [] data; } return success; } int K3bDevice::Device::nextWritableAddress() const { unsigned char* data = 0; unsigned int dataLen = 0; int nwa = -1; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; // // The state of the last session has to be "empty" (0x0) or "incomplete" (0x1) // The procedure here is taken from the dvd+rw-tools and wodim // if( !(inf->border & 0x2) ) { // the incomplete track number is the first track in the last session (the empty session) int nextTrack = inf->first_track_l|inf->first_track_m<<8; unsigned char* trackData = 0; unsigned int trackDataLen = 0; // Read start address of the incomplete track if( readTrackInformation( &trackData, trackDataLen, 0x1, nextTrack ) ) { nwa = from4Byte( &trackData[8] ); delete [] trackData; } // Read start address of the invisible track else if ( readTrackInformation( &trackData, trackDataLen, 0x1, 0xff ) ) { nwa = from4Byte( &trackData[8] ); delete [] trackData; } } delete [] data; } return nwa; } TQCString K3bDevice::Device::mediaId( int mediaType ) const { TQCString id; if( mediaType & MEDIA_CD_ALL ) { // FIXME: } else if( mediaType & MEDIA_DVD_MINUS_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen, 0x0E ) ) { if( data[4+16] == 3 && data[4+24] == 4 ) { id.sprintf( "%6.6s%-6.6s", data+4+17, data+4+25 ); } delete [] data; } } else if( mediaType & MEDIA_DVD_PLUS_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen, 0x11 ) || readDvdStructure( &data, dataLen, 0x0 ) ) { id.sprintf( "%8.8s/%3.3s", data+23, data+31 ); delete [] data; } } else if( mediaType & MEDIA_BD_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscStructure( &data, dataLen, 1, 0 ) ) { if( data[4+0] == 'D' && data[4+1] == 'I' ) id.sprintf ("%6.6s/%-3.3s", data+4+100, data+4+106 ); delete [] data; } } return id; } // int K3bDevice::Device::ioctl( int request, ... ) const // { // int r = -1; // #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) // d->mutex.lock(); // va_list ap; // va_start( ap, request ); // r = ::ioctl( d->deviceFd, request, ap ); // va_end( ap ); // d->mutex.unlock(); // #endif // return r; // } void K3bDevice::Device::usageLock() const { d->mutex.lock(); } void K3bDevice::Device::usageUnlock() const { d->mutex.unlock(); }