/*
    SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "k3bdevice.h"
#include "k3bdeviceglobals.h"
#include "k3btrack.h"
#include "k3btoc.h"
#include "k3bdiskinfo.h"
#include "k3bdiskinfo_p.h"
#include "k3bmmc.h"
#include "k3bscsicommand.h"
#include "k3bcrc.h"

#include "config-k3b.h"

#include <Solid/Device>
#include <Solid/OpticalDrive>
#include <Solid/Block>
#include <Solid/StorageAccess>
#ifdef Q_OS_NETBSD
#include <Solid/GenericInterface>
#endif

#include <qglobal.h>
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QStringList>

#include <sys/types.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <math.h>
#include <stdarg.h>
#include <limits.h>


#ifdef Q_OS_LINUX

#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,70)
typedef unsigned char u8;
#endif

#undef __STRICT_ANSI__
#include <linux/cdrom.h>
#define __STRICT_ANSI__

#endif // Q_OS_LINUX

#ifdef Q_OS_FREEBSD
#include <stdio.h>
#include <camlib.h>
#define CD_FRAMESIZE_RAW 2352
#endif

#ifdef Q_OS_NETBSD
#include <sys/cdio.h>
#endif

#ifdef HAVE_RESMGR
extern "C" {
#include <resmgr.h>
}
#endif

#ifdef Q_OS_FREEBSD
#define HANDLE_DEFAULT_VALUE 0
#endif
#ifdef Q_OS_WIN32
#define HANDLE_DEFAULT_VALUE INVALID_HANDLE_VALUE
#endif
#ifdef Q_OS_LINUX
#define HANDLE_DEFAULT_VALUE -1
#endif
#ifdef Q_OS_NETBSD
#define HANDLE_DEFAULT_VALUE -1
#endif

//
// Very evil hacking: force the speed values to be accurate
// as long as "they" do not introduce other "broken" DVD
// speeds like 2.4 this works fine
//
namespace {
    int fixupDvdWritingSpeed( int speed )
    {
        //
        // Some writers report their speeds in 1000 bytes per second instead of 1024.
        //
        if( speed % K3b::Device::SPEED_FACTOR_DVD == 0 )
            return speed;

        else if( speed % 1352 == 0 )
            return speed*K3b::Device::SPEED_FACTOR_DVD/1352;

        // has to be 2.4x speed
        else
            return 3324;
    }
}

class K3b::Device::Device::Private
{
public:
    Private()
        : deviceHandle(HANDLE_DEFAULT_VALUE),
          openedReadWrite(false),
          burnfree(false) {
    }

    Solid::Device solidDevice;

    QString vendor;
    QString description;
    QString version;
    int maxReadSpeed;
    int maxWriteSpeed;
    int currentWriteSpeed;

    bool dvdMinusTestwrite;

    int bufferSize;

    WritingModes writeModes;

    QString blockDevice;
    QString genericDevice;

    MediaTypes readCapabilities;
    MediaTypes writeCapabilities;
    MediaTypes supportedProfiles;
    Handle deviceHandle;
    bool openedReadWrite;
    bool burnfree;

    QMutex mutex;
    QMutex openCloseMutex;
};

#ifdef Q_OS_FREEBSD
K3b::Device::Device::Handle K3b::Device::openDevice( const char* name, bool write )
{
    K3b::Device::Device::Handle handle = cam_open_device (name, O_RDWR);
        qDebug() << "(K3b::Device::openDevice) open device " << name
                 << ((handle)?" succeeded.":" failed.") << Qt::endl;
    return handle;
}
#endif


#if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD)
K3b::Device::Device::Handle K3b::Device::openDevice( const char* name, bool write )
{
    K3b::Device::Device::Handle fd = HANDLE_DEFAULT_VALUE;
    int flags = O_NONBLOCK | O_CLOEXEC;
    if( write )
        flags |= O_RDWR;
    else
        flags |= O_RDONLY;

#ifdef HAVE_RESMGR
    // first try resmgr
    fd = ::rsm_open_device( name, flags );
    //  qDebug() << "(K3b::Device::Device) resmgr open: " << fd;
#endif

    if( fd < 0 )
        fd = ::open( name, flags );

    if( fd < 0 ) {
        qDebug() << "(K3b::Device::Device) could not open device "
                 << name << ( write ? " for writing" : " for reading" ) << Qt::endl;
        qDebug() << "                    (" << QString::fromLocal8Bit( ::strerror(errno) ) << ")";
        fd = HANDLE_DEFAULT_VALUE;

        // at least open it read-only (which is sufficient for kernels < 2.6.8 anyway)
        if( write )
            return openDevice( name, false );
    }

    return fd;
}
#endif

#ifdef Q_OS_WIN32
#define NAME_COUNT  25

K3b::Device::Device::Handle K3b::Device::openDevice( const char* name, bool write )
{
    bool status = false;
    K3b::Device::Device::Handle deviceHandle = HANDLE_DEFAULT_VALUE;
    char string[NAME_COUNT + 1];
    // check if name is already a device name
    if (name[0] == '\\')
        strncpy(string, name, NAME_COUNT);
    else
        _snprintf(string, NAME_COUNT, "\\\\.\\%s", name);
    deviceHandle = CreateFileA(string,
        GENERIC_READ | GENERIC_WRITE , // at least inquiry needs write access
        FILE_SHARE_READ | (write ? FILE_SHARE_WRITE : 0),
        nullptr,
        OPEN_EXISTING,
        0,
        nullptr);

    if( deviceHandle == INVALID_HANDLE_VALUE )
        deviceHandle = CreateFileA(string,
            GENERIC_READ,
            FILE_SHARE_READ,
            nullptr,
            OPEN_EXISTING,
            0,
            nullptr);

    if (deviceHandle == INVALID_HANDLE_VALUE) {
        int errorCode = GetLastError();
        qDebug() << "Error opening " << string << "Error:" << errorCode << Qt::endl;
        return HANDLE_DEFAULT_VALUE;
    }

    return deviceHandle;
}
#endif


K3b::Device::Device::Device( const Solid::Device& dev )
{
#ifdef Q_OS_NETBSD
    const Solid::GenericInterface *gi = dev.as<Solid::GenericInterface>();
#endif
    d = new Private;
    d->solidDevice = dev;
    // TODO: block device address might not be "stdio:/path"
#ifndef Q_OS_NETBSD
    d->blockDevice = dev.as<Solid::Block>()->device();
#else
    if (gi->propertyExists("block.netbsd.raw_device"))
        d->blockDevice = gi->property("block.netbsd.raw_device").toString();
    else
        d->blockDevice = dev.as<Solid::Block>()->device();
#endif
    d->writeModes = {};
    d->maxWriteSpeed = 0;
    d->maxReadSpeed = 0;
    d->burnfree = false;
    d->dvdMinusTestwrite = true;
    d->bufferSize = 0;
}


K3b::Device::Device::~Device()
{
    close();
    delete d;
}


QString K3b::Device::Device::vendor() const
{
    return d->vendor;
}


QString K3b::Device::Device::description() const
{
    return d->description;
}


QString K3b::Device::Device::version() const
{
    return d->version;
}


bool K3b::Device::Device::dvdMinusTestwrite() const
{
    return d->dvdMinusTestwrite;
}


int K3b::Device::Device::maxReadSpeed() const
{
    return d->maxReadSpeed;
}


int K3b::Device::Device::currentWriteSpeed() const
{
    return d->currentWriteSpeed;
}


int K3b::Device::Device::bufferSize() const
{
    return d->bufferSize;
}


QString K3b::Device::Device::blockDeviceName() const
{
    return d->blockDevice;
}


int K3b::Device::Device::maxWriteSpeed() const
{
    return d->maxWriteSpeed;
}


void K3b::Device::Device::setCurrentWriteSpeed( int s )
{
    d->currentWriteSpeed = s;
}


void K3b::Device::Device::setMaxReadSpeed( int s )
{
    d->maxReadSpeed = s;
}


void K3b::Device::Device::setMaxWriteSpeed( int s )
{
    d->maxWriteSpeed = s;
}


Solid::Device K3b::Device::Device::solidDevice() const
{
    return d->solidDevice;
}


Solid::StorageAccess* K3b::Device::Device::solidStorage() const
{
     QList<Solid::Device> storages = Solid::Device::listFromType( Solid::DeviceInterface::StorageAccess, d->solidDevice.udi() );
     if( storages.isEmpty() )
         return nullptr;
     else
         return storages.first().as<Solid::StorageAccess>();
}


bool K3b::Device::Device::init( bool bCheckWritingModes )
{
    qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": init()";

    //
    // they all should read CD-ROM.
    //
    d->readCapabilities = MEDIA_CD_ROM;
    d->writeCapabilities = {};
    d->supportedProfiles = {};

    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 = reinterpret_cast<struct inquiry *>(buf);
    cmd[0] = MMC_INQUIRY;
    cmd[4] = sizeof(buf);
    cmd[5] = 0;
    if( cmd.transport( TR_DIR_READ, buf, sizeof(buf) ) ) {
        qCritical() << "(K3b::Device::Device) Unable to do inquiry." << Qt::endl;
        close();
        return false;
    }
    else {
        d->vendor = QString::fromLatin1( reinterpret_cast<char *>(inq->vendor), 8 ).trimmed();
        d->description = QString::fromLatin1( reinterpret_cast<char *>(inq->product), 16 ).trimmed();
        d->version = QString::fromLatin1( reinterpret_cast<char *>(inq->revision), 4 ).trimmed();
    }

    if( d->vendor.isEmpty() )
        d->vendor = "UNKNOWN";
    if( d->description.isEmpty() )
        d->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 changing
    // 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();

    d->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 K3b::Device::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 ) {
        qDebug() << "Error while retrieving capabilities.";
        close();
        return false;
    }

    d->readCapabilities |= MEDIA_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
#ifdef Q_OS_WIN32
    qDebug() << __FUNCTION__ << "to be implemented";
#endif
    return true;
}


void K3b::Device::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") ) ) {
        d->writeModes = WRITINGMODE_TAO;
        d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
        d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
        d->maxWriteSpeed = 4;
        d->maxReadSpeed = 12;
        d->bufferSize = 1024;
        d->burnfree = false;
    }
    else if( vendor().startsWith("TEAC") ) {
        if( description().startsWith("CD-R56S") ) {
            d->writeModes |= WRITINGMODE_TAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 6;
            d->maxReadSpeed = 24;
            d->bufferSize = 1302;
            d->burnfree = false;
        }
        if( description().startsWith("CD-R58S") ) {
            d->writeModes |= WRITINGMODE_TAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 8;
            d->maxReadSpeed = 24;
            d->bufferSize = 4096;
            d->burnfree = false;
        }
    }
    else if( vendor().startsWith("MATSHITA") ) {
        if( description().startsWith("CD-R   CW-7501") ) {
            d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 2;
            d->maxReadSpeed = 4;
            d->bufferSize = 1024;
            d->burnfree = false;
        }
        if( description().startsWith("CD-R   CW-7502") ) {
            d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 4;
            d->maxReadSpeed = 8;
            d->bufferSize = 1024;
            d->burnfree = false;
        }
        else if( description().startsWith("CD-R56S") ) {
            d->writeModes |= WRITINGMODE_TAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 6;
            d->maxReadSpeed = 24;
            d->bufferSize = 1302;
            d->burnfree = false;
        }
    }
    else if( vendor().startsWith("HP") ) {
        if( description().startsWith("CD-Writer 6020") ) {
            d->writeModes = WRITINGMODE_TAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 2;
            d->maxReadSpeed = 6;
            d->bufferSize = 1024;
            d->burnfree = false;
        }
    }
    else if( vendor().startsWith( "PHILIPS" ) ) {
        if( description().startsWith( "CDD2600" ) ) {
            d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO;
            d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R;
            d->maxWriteSpeed = 2;
            d->maxReadSpeed = 6;
            d->bufferSize = 1024;
            d->burnfree = false;
        }
    }
}


bool K3b::Device::Device::dao() const
{
    return d->writeModes & WRITINGMODE_SAO;
}


bool K3b::Device::Device::supportsRawWriting() const
{
    return( writingModes() & (WRITINGMODE_RAW|WRITINGMODE_RAW_R16|WRITINGMODE_RAW_R96P|WRITINGMODE_RAW_R96R) );
}


bool K3b::Device::Device::writesCd() const
{
    return ( d->writeCapabilities & MEDIA_CD_R ) && ( d->writeModes & WRITINGMODE_TAO );
}


bool K3b::Device::Device::burner() const
{
    return ( writesCd() || writesDvd() );
}


bool K3b::Device::Device::writesCdrw() const
{
    return d->writeCapabilities & MEDIA_CD_RW;
}


bool K3b::Device::Device::writesDvd() const
{
    return ( writesDvdPlus() || writesDvdMinus() );
}


bool K3b::Device::Device::writesDvdPlus() const
{
    return d->writeCapabilities & (MEDIA_DVD_PLUS_R|MEDIA_DVD_PLUS_RW);
}


bool K3b::Device::Device::writesDvdMinus() const
{
    return d->writeCapabilities & (MEDIA_DVD_R|MEDIA_DVD_RW);
}


bool K3b::Device::Device::readsDvd() const
{
    return d->readCapabilities & MEDIA_DVD_ROM;
}


K3b::Device::DeviceTypes K3b::Device::Device::type() const
{
    DeviceTypes r = {};
    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;
}


K3b::Device::MediaTypes K3b::Device::Device::readCapabilities() const
{
    return d->readCapabilities;
}


K3b::Device::MediaTypes K3b::Device::Device::writeCapabilities() const
{
    return d->writeCapabilities;
}


K3b::Device::WritingModes K3b::Device::Device::writingModes() const
{
    return d->writeModes;
}


bool K3b::Device::Device::burnproof() const
{
    return burnfree();
}


bool K3b::Device::Device::burnfree() const
{
    return d->burnfree;
}


bool K3b::Device::Device::isDVD() const
{
    if( readsDvd() )
        return( mediaType() & MEDIA_DVD_ALL );
    else
        return false;
}


int K3b::Device::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;

    UByteArray data;

    if( readDiscInformation( data ) ) {
        disc_info_t* inf = reinterpret_cast<disc_info_t *>(data.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;
        }
    }

    if( needToClose )
        close();

    return ret;
}


int K3b::Device::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;

    UByteArray data;

    int m = mediaType();
    if( m & MEDIA_CD_ALL ) {
        //
        // Although 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, 1, 0, 0 ) ) {
            ret = data[3];
        }
        else {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": could not get session info !";
        }
    }
    else if ( m & ( MEDIA_DVD_PLUS_RW|MEDIA_DVD_RW_OVWR|MEDIA_BD_RE ) ) {
        // fabricate value
        int e = isEmpty();
        return ( e == STATE_COMPLETE ? 1 : 0 );
    }
    else {
        if( readDiscInformation( data ) ) {
            ret = int( data[9]<<8 | data[4] );

            // do only count complete sessions
            if( (data[2]>>2) != 3 )
                ret--;
        }
    }

    return ret;
}


K3b::Device::Track::DataMode K3b::Device::Device::getDataMode( const K3b::Msf& sector ) const
{
    bool needToClose = !isOpen();

    Track::DataMode 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;
}



K3b::Device::Track::DataMode K3b::Device::Device::getTrackDataMode( const K3b::Device::Track& track ) const
{
    return getDataMode( track.firstSector() );
}


K3b::Device::Toc K3b::Device::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.setFirstSector( 0 );
                track.setLastSector( size.lba() );
                track.setSession( 1 );
                track.setType( Track::TYPE_DATA );
                track.setMode( Track::DVD );
                track.setCopyPermitted( mt != MEDIA_DVD_ROM );
                track.setPreEmphasis( mt != MEDIA_DVD_ROM );

                toc.append( track );
            }
            else
                qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                         << "READ CAPACITY for toc failed." << Qt::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 ) {
        qDebug() << "(K3b::Device::readDvdToc) no dvdram support";
    }


    else if( mt & MEDIA_CD_ALL ) {
        bool success = readRawToc( toc );
        if( !success ) {
            success = readFormattedToc( toc, mt );

#ifdef Q_OS_LINUX
            if( !success ) {
                qDebug() << "(K3b::Device::Device) MMC READ TOC failed. falling back to cdrom.h.";
                readTocLinux(toc);
            }
#endif

            if( success )
                fixupToc( toc );
        }
    }

    if( needToClose )
        close();

    return toc;
}


void K3b::Device::Device::readIsrcMcn( K3b::Device::Toc& toc ) const
{
    // read MCN and ISRC of all tracks
    QByteArray mcn;
    if( readMcn( mcn ) ) {
        toc.setMcn( mcn );
        qDebug() << "(K3b::Device::Device) found MCN: " << mcn;
    }
    else
        qDebug() << "(K3b::Device::Device) no MCN found.";

    for( int i = 1; i <= toc.count(); ++i ) {
        QByteArray isrc;
        if( toc[i-1].type() == Track::TYPE_AUDIO ) {
            if( readIsrc( i, isrc ) ) {
                qDebug() << "(K3b::Device::Device) found ISRC for track " << i << ": " << isrc;
                toc[i-1].setIsrc( isrc );
            }
            else
                qDebug() << "(K3b::Device::Device) no ISRC found for track " << i;
        }
    }
}


bool K3b::Device::Device::readFormattedToc( K3b::Device::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();

    int lastTrack = 0;

    UByteArray data;
    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 ) ) {
            lastTrack = int( data[11]<<8 | data[6] );

            if( readTrackInformation( data, 1, lastTrack ) ) {
                track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());

                if( trackInfo->blank ) {
                    lastTrack--;
                }

                success = true;
            }
            else
                return false;
        }
        else
            return false;
    }
    else {
        if( readTocPmaAtip( data, 0, 0, 1 ) ) {

            if( data.size() < 4 ) {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": formatted toc data too small.";
            }
            else if( data.size() != ( int(sizeof(toc_track_descriptor)) * int(data[3]+1) ) + 4 ) {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": invalid formatted toc data length: "
                         << (data.size()-2) << Qt::endl;
            }
            else {
                lastTrack = data[3];
                toc_track_descriptor* td = reinterpret_cast<toc_track_descriptor *>(&data[4]);
                for( 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.setFirstSector( from4Byte( td[i].start_adr ) );
                    track.setLastSector( from4Byte( td[i+1].start_adr ) - 1 );
                    control = td[i].control;

                    track.setType( (control & 0x4) ? Track::TYPE_DATA : Track::TYPE_AUDIO );
                    track.setMode( getTrackDataMode( track ) );
                    track.setCopyPermitted( control & 0x2 );
                    track.setPreEmphasis( control & 0x1 );

                    toc.append( track );
                }

                success = true;
            }
        }
    }


    //
    // Try to get information for all the tracks
    //
    for( int i = 0; i < lastTrack; ++i ) {
        if( toc.count() < i+1 )
            toc.append( Track() );

        UByteArray trackData;
        if( readTrackInformation( trackData, 1, i+1 ) ) {
            track_info_t* trackInfo = reinterpret_cast<track_info_t *>(trackData.data());

            toc[i].setFirstSector( from4Byte( trackInfo->track_start ) );

            if( i > 0 && toc[i-1].lastSector() == 0 )
                toc[i-1].setLastSector( toc[i].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].setLastSector( toc[i].firstSector() + from4Byte( trackInfo->track_size ) - 1 );

            if( trackInfo->nwa_v ) {
                toc[i].setNextWritableAddress( from4Byte( trackInfo->next_writable ) );
                toc[i].setFreeBlocks( from4Byte( trackInfo->free_blocks ) );
            }

            toc[i].setSession( 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].setType( (control & 0x4) ? Track::TYPE_DATA : Track::TYPE_AUDIO );
                toc[i].setMode( getTrackDataMode( toc[i] ) );
            }
            else {
                toc[i].setType( Track::TYPE_DATA );
                toc[i].setMode( Track::DVD );
            }
            toc[i].setCopyPermitted( control & 0x2 );
            toc[i].setPreEmphasis( control & 0x1 );
        }
        else if( !(mt & MEDIA_CD_ALL) ) {
            success = false;
        }
    }

    // this can only happen with DVD media
    if( !toc.isEmpty() && toc.last().lastSector() == 0 ) {
        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " no track length for the last non-empty track.";
        UByteArray trackData;
        if( readTrackInformation( trackData, 1, lastTrack+1 ) ) {
            track_info_t* trackInfo = reinterpret_cast<track_info_t *>(trackData.data());
            toc.last().setLastSector( from4Byte( trackInfo->track_start ) - 1 );
        }
    }

    if( needToClose )
        close();

    return success;
}


bool K3b::Device::Device::readRawToc( K3b::Device::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
        //

        UByteArray data;

        if( readTocPmaAtip( data, 2, false, 1 ) ) {
            if( data.size() > 4 ) {
                success = true;

                toc_raw_track_descriptor* tr = reinterpret_cast<toc_raw_track_descriptor *>(&data[4]);

                //
                // debug the raw toc data
                //
                qDebug() << "Session |  ADR   | CONTROL|  TNO   | POINT  |  Min   |  Sec   | Frame  |  Zero  |  PMIN  |  PSEC  | PFRAME |";
                for( int i = 0; i < (data.size()-4)/int(sizeof(toc_raw_track_descriptor)); ++i ) {
                    QString s;
                    s += QString( " %1 |" ).arg( int(tr[i].session_number), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].adr), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].control), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].tno), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].point), 6, 16 );
                    s += QString( " %1 |" ).arg( int(tr[i].min), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].sec), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].frame), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].zero), 6, 16 );
                    s += QString( " %1 |" ).arg( int(tr[i].p_min), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].p_sec), 6 );
                    s += QString( " %1 |" ).arg( int(tr[i].p_frame), 6 );
                    qDebug() << s;
                }

                //
                // First we try to determine if the raw toc data uses BCD values
                //
                int isBcd = rawTocDataWithBcdValues( data );
                if( isBcd == -1 ) {
                    return false;
                }

                K3b::Msf sessionLeadOut;

                for( unsigned i = 0; i < (data.size()-4)/unsigned(sizeof(toc_raw_track_descriptor)); ++i ) {
                    if( tr[i].adr == 1 && tr[i].point <= 0x63 ) {
                        // track
                        Track track;
                        track.setSession( tr[i].session_number );

                        // :( We use 00:00:00 == 0 lba)
                        if( isBcd )
                            track.setFirstSector( K3b::Msf( K3b::Device::fromBcd(tr[i].p_min),
                                                            K3b::Device::fromBcd(tr[i].p_sec),
                                                            K3b::Device::fromBcd(tr[i].p_frame) ) - 150 );
                        else
                            track.setFirstSector( K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150 );

                        track.setType( tr[i].control & 0x4 ? Track::TYPE_DATA : Track::TYPE_AUDIO );
                        track.setMode( track.type() == Track::TYPE_DATA ? getTrackDataMode(track) : Track::UNKNOWN );
                        track.setCopyPermitted( tr[i].control & 0x2 );
                        track.setPreEmphasis( 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].setLastSector( 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].setLastSector( 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( K3b::Device::fromBcd(tr[i].p_min),
                                                       K3b::Device::fromBcd(tr[i].p_sec),
                                                       K3b::Device::fromBcd(tr[i].p_frame) ) - 150;
                        else
                            sessionLeadOut = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150;
                    }
                }

                qDebug() << blockDeviceName() << ": setting last sector of last track to " << (sessionLeadOut-1).lba();

                // set the last track's last sector
                if( !toc.isEmpty() )
                    toc[toc.count()-1].setLastSector( sessionLeadOut - 1 );
            }
            else
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " empty raw toc.";
        }
    }

    if( needToClose )
        close();

    return success;
}


int K3b::Device::Device::rawTocDataWithBcdValues( const UByteArray& data ) const
{
    const toc_raw_track_descriptor* tr = reinterpret_cast<const 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 i = 0; i < (data.size()-4)/unsigned(sizeof(toc_raw_track_descriptor)); ++i ) {
        if( tr[i].adr == 1 && tr[i].point <= 0xa2) {
            if( !K3b::Device::isValidBcd(tr[i].p_min) ||
                !K3b::Device::isValidBcd(tr[i].p_sec) ||
                !K3b::Device::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(K3b::Device::fromBcd(tr[i].p_sec)) >= 60 ||
                     int(K3b::Device::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 i = 0; i < (data.size()-4)/unsigned(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 i = 0; i < (data.size()-4)/unsigned(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( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ) <
                        lastTrackBcd )
                        notBcd = true;

                    lastTrackBcd = K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::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( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) );
                }
            }
        }

        // check the last track
        if( sessionLeadOutHex < lastTrackHex )
            notHex = true;
        if( sessionLeadOutBcd < lastTrackBcd )
            notBcd = true;
    }


    if( !notBcd && !notHex ) {
        qDebug() << "(K3b::Device::Device) need to compare raw toc to formatted toc. :(";
        //
        // 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
        //
        K3b::Device::Toc formattedToc;
        if( readFormattedToc( formattedToc, MEDIA_CD_ROM ) ) {
            for( unsigned i = 0; i < (data.size()-4)/unsigned(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( int(track) > formattedToc.count() ) {
                        notHex = true;
                        break;
                    }

                    K3b::Msf posHex( tr[i].p_min,
                                     tr[i].p_sec,
                                     tr[i].p_frame );
                    K3b::Msf posBcd( K3b::Device::fromBcd(tr[i].p_min),
                                     K3b::Device::fromBcd(tr[i].p_sec),
                                     K3b::Device::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 )
        qDebug() << "(K3b::Device::Device) found invalid bcd values. No bcd toc.";
    if( notHex )
        qDebug() << "(K3b::Device::Device) found invalid hex values. No hex toc.";

    if( notBcd == notHex ) {
        qDebug() << "(K3b::Device::Device) unable to determine if hex (" << notHex << ") or bcd (" << notBcd << ").";
        if( !notHex ) {
            qDebug() << "Assuming hex encoding in favor of newer drives and the more reliable raw toc.";
            return 0;
        }
        return -1;
    }
    else if( notBcd )
        return 0;
    else
        return 1;
}


K3b::Device::CdText K3b::Device::Device::readCdText() const
{
    return CdText( readRawCdText() );
}


QByteArray K3b::Device::Device::readRawCdText( bool* success ) const
{
    // if the device is already opened we do not close it
    // to allow fast multiple method calls in a row
    bool needToClose = !isOpen();

    QByteArray textData;

    if ( success )
        *success = false;

    if( open() ) {
        UByteArray data;

        if( readTocPmaAtip( data, 5, false, 0 ) ) {
            // we need more than the header and a multiple of 18 bytes to have valid CD-TEXT
            if( data.size() > 4 && data.size()%18 == 4 ) {
                textData.append( QByteArray( reinterpret_cast<char*>(data.data()), data.size() ) );
                if ( success )
                    *success = true;
            }
            else {
                qDebug() << "invalid CD-TEXT length: " << data.size();
            }
        }

        if( needToClose )
            close();
    }

    return textData;
}


#ifdef Q_OS_LINUX
// fallback
bool K3b::Device::Device::readTocLinux( K3b::Device::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->deviceHandle, CDROMREADTOCHDR, &tochdr ) ) {
            qDebug() << "(K3b::Device::Device) could not get toc header !";
            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->deviceHandle, CDROMREADTOCENTRY, &tocentry ) ) {
                    qDebug() << "(K3b::Device::Device) error reading tocentry " << i;
                    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.setPreEmphasis( control & 0x1 );
                    track.setCopyPermitted( control & 0x2 );
                    toc.append( track );
                }
                Track::TrackType trackType = Track::TYPE_UNKNOWN;
                Track::DataMode trackMode = Track::UNKNOWN;
                if( (control & 0x04 ) && (tocentry.cdte_track != CDROM_LEADOUT) ) {
                    trackType = Track::TYPE_DATA;
                    if( mode == 1 )
                        trackMode = Track::MODE1;
                    else if( mode == 2 )
                        trackMode = Track::MODE2;

                    Track::DataMode tm = getDataMode(startSec);
                    if( tm != Track::UNKNOWN )
                        trackMode = tm;
                }
                else
                    trackType = Track::TYPE_AUDIO;

                lastTrack = Track( startSec, startSec, trackType, trackMode );
            }
        }

        if( needToClose )
            close();
    }
    else
        success = false;

    usageUnlock();

    return success;
}
#endif // Q_OS_LINUX


bool K3b::Device::Device::fixupToc( K3b::Device::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 ) {
        qDebug() << "(K3b::Device::Device) fixup multisession toc...";

        //
        // 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
        //

        UByteArray data;
        if( readTocPmaAtip( data, 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(data[6])-2].setLastSector( from4Byte( &data[8] ) - 11400 - 1 );

            success = true;
        }
        else
            qDebug() << "(K3b::Device::Device) FIXUP TOC failed.";
    }

    return success;
}


bool K3b::Device::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->deviceHandle, 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->deviceHandle, 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 )
        qDebug() << "(K3b::Device::Device) MMC ALLOW MEDIA REMOVAL failed.";

    return ( r == 0 );
}

bool K3b::Device::Device::rewritable() const
{
    UByteArray data;

    if( readDiscInformation( data ) ) {
        disc_info_t* inf = reinterpret_cast<disc_info_t *>(data.data());
        bool e = inf->erasable;

        return e;
    }
    else
        return false;
}


bool K3b::Device::Device::eject() const
{
#ifdef Q_OS_NETBSD
    bool success = false;
    bool needToClose = !isOpen();
    int arg = 0;

    usageLock();
    if( open() ) {
        if ( ::ioctl( d->deviceHandle, DIOCEJECT, &arg ) >= 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( TR_DIR_WRITE );

    cmd[0] = MMC_START_STOP_UNIT;
    cmd[5] = 0; // Necessary to set the proper command length
    cmd[4] = 0x2;      // eject medium LoEj = 1, Start = 0
    if (!cmd.transport( TR_DIR_WRITE )) {
        return true;
    }

#if defined(Q_OS_LINUX)
    bool success = false;
    bool needToClose = !isOpen();

    usageLock();
    if( open() ) {
        if( ::ioctl( d->deviceHandle, CDROMEJECT ) >= 0 )
            success = true;
        if( needToClose )
            close();
    }
    usageUnlock();
    return success;
#else
    return false;
#endif
}


bool K3b::Device::Device::load() const
{
#ifdef Q_OS_NETBSD
    bool success = false;
    bool needToClose = !isOpen();
    int arg = 0;

    usageLock();
    if( open() ) {
        if ( ::ioctl( d->deviceHandle, 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->deviceHandle, 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 K3b::Device::Device::setAutoEjectEnabled( bool enabled ) const
{
    bool success = false;
#ifdef Q_OS_LINUX

    bool needToClose = !isOpen();
    usageLock();
    if ( open() ) {
        success = ( ::ioctl( d->deviceHandle, CDROMEJECT_SW, enabled ? 1 : 0 ) == 0 );
        if ( needToClose ) {
            close();
        }
    }
    usageUnlock();
#endif
    return success;
}


K3b::Device::Device::Handle K3b::Device::Device::handle() const
{
    return d->deviceHandle;
}


bool K3b::Device::Device::open( bool write ) const
{
    if( d->openedReadWrite != write )
        close();

    QMutexLocker ml( &d->openCloseMutex );

    d->openedReadWrite = write;

    if( d->deviceHandle == HANDLE_DEFAULT_VALUE)
        d->deviceHandle = openDevice( QFile::encodeName(blockDeviceName()), write );

    return ( d->deviceHandle != HANDLE_DEFAULT_VALUE);
}


void K3b::Device::Device::close() const
{
    QMutexLocker ml( &d->openCloseMutex );

    if( d->deviceHandle == HANDLE_DEFAULT_VALUE)
        return;

#if defined(Q_OS_FREEBSD)
    cam_close_device(d->deviceHandle);
#elif defined(Q_OS_WIN32)
    CloseHandle(d->deviceHandle);
#else
    ::close( d->deviceHandle );
#endif
    d->deviceHandle = HANDLE_DEFAULT_VALUE;
}


bool K3b::Device::Device::isOpen() const
{
    return ( d->deviceHandle != HANDLE_DEFAULT_VALUE);
}


int K3b::Device::Device::supportedProfiles() const
{
    return d->supportedProfiles;
}


int K3b::Device::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 ) ) {
        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                 << " GET_CONFIGURATION failed." << Qt::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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                     << " current profile 0. Checking current profile list instead." << Qt::endl;
            UByteArray data;
            if( getFeature( data, 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;
                    }
                }
            }
        }

        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;
        }
    }
}


K3b::Device::DiskInfo K3b::Device::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() ) {

        UByteArray data;

        //
        // 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.d->diskState = STATE_NO_MEDIA;
            inf.d->mediaType = MEDIA_NONE;
            inf.d->currentProfile = MEDIA_NONE;
        }
        else
            inf.d->currentProfile = currentProfile();

        if( inf.diskState() != STATE_NO_MEDIA ) {

            if( readDiscInformation( data ) ) {
                disc_info_t* dInf = reinterpret_cast<disc_info_t *>(data.data());
                //
                // Copy the needed values from the disk_info struct
                //
                switch( dInf->status ) {
                case 0:
                    inf.d->diskState = STATE_EMPTY;
                    break;
                case 1:
                    inf.d->diskState = STATE_INCOMPLETE;
                    break;
                case 2:
                    inf.d->diskState = STATE_COMPLETE;
                    break;
                default:
                    inf.d->diskState = STATE_UNKNOWN;
                    break;
                }

                switch( dInf->border ) {
                case 0:
                    inf.d->lastSessionState = STATE_EMPTY;
                    break;
                case 1:
                    inf.d->lastSessionState = STATE_INCOMPLETE;
                    break;
                case 2:
                    inf.d->lastSessionState = STATE_COMPLETE;
                    break;
                default:
                    inf.d->lastSessionState = STATE_UNKNOWN;
                    break;
                }

                switch( dInf->bg_f_status&0x3 ) {
                case 0x0:
                    inf.d->bgFormatState = BG_FORMAT_NONE;
                    break;
                case 0x1:
                    inf.d->bgFormatState = BG_FORMAT_INCOMPLETE;
                    break;
                case 0x2:
                    inf.d->bgFormatState = BG_FORMAT_IN_PROGRESS;
                    break;
                case 0x3:
                    inf.d->bgFormatState = BG_FORMAT_COMPLETE;
                    break;
                }

                inf.d->numTracks = (dInf->last_track_l & 0xff) | (dInf->last_track_m<<8 & 0xff00);
                if( inf.diskState() == STATE_EMPTY )
                    inf.d->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.d->numTracks--;  // do not count the invisible track

                inf.d->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.d->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.d->usedCapacity = K3b::Msf( dInf->lead_in_m + dInf->lead_in_r*60,
                                                    dInf->lead_in_s,
                                                    dInf->lead_in_f ) - 4500;
            }
            else {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                         << " fabricating disk information for a stupid device." << Qt::endl;
                Toc toc = readToc();
                if( !toc.isEmpty() ) {
                    inf.d->diskState = STATE_COMPLETE;
                    inf.d->lastSessionState = STATE_COMPLETE;
                    inf.d->numTracks = toc.count();
                    inf.d->capacity = inf.d->usedCapacity = toc.length();
                }
            }


            //
            // The mediatype needs to be set
            //
            inf.d->mediaType = mediaType();

            // At least some Plextor drives return profile NONE for CD media
            // or CD_ROM for writable media
            if( inf.d->mediaType & (MEDIA_UNKNOWN|MEDIA_NONE|MEDIA_CD_ROM) ) {
                // probably it is a CD
                if( inf.rewritable() )
                    inf.d->mediaType = MEDIA_CD_RW;
                else if( inf.empty() || inf.appendable() )
                    inf.d->mediaType = MEDIA_CD_R;
                else
                    inf.d->mediaType = MEDIA_CD_ROM;
            }

            if( inf.d->mediaType & MEDIA_DVD_ALL ) {
                if( readDvdStructure( data ) ) {
                    // 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] );

                    qDebug() << "First sec data area: " << sda.toString()
                             << " (LBA " << QString::number(sda.lba())
                             << ") (" << QString::number(sda.mode1Bytes()) << Qt::endl;
                    qDebug() << "Last sec data area: " << eda.toString()
                             << " (LBA " << QString::number(eda.lba())
                             << ") (" << QString::number(eda.mode1Bytes()) << " Bytes)" << Qt::endl;
                    qDebug() << "Last sec layer 1: " << ea0.toString()
                             << " (LBA " << QString::number(ea0.lba())
                             << ") (" << QString::number(ea0.mode1Bytes()) << " Bytes)" << Qt::endl;


                    K3b::Msf da0 = ea0 - sda + 1;
                    K3b::Msf da1 = eda - ea0;
                    qDebug() << "Layer 1 length: " << da0.toString()
                             << " (LBA " << QString::number(da0.lba())
                             << ") (" << QString::number(da0.mode1Bytes()) << " Bytes)" << Qt::endl;
                    qDebug() << "Layer 2 length: " << da1.toString()
                             << " (LBA " << QString::number(da1.lba())
                             << ") (" << QString::number(da1.mode1Bytes()) << " Bytes)" << Qt::endl;

                    inf.d->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.d->firstLayerSize = da0;
                    else
                        inf.d->firstLayerSize = 0;
                }
                else {
                    qDebug() << "(K3b::Device::Device) Unable to read DVD structure for num of layers.";
                    inf.d->numLayers = ( (inf.d->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.d->numSessions = sessions;
                else
                    qDebug() << "(K3b::Device::Device) could not get session info via READ TOC/PMA/ATIP.";
            }
            else
                inf.d->numSessions = 0;

            inf.d->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.d->capacity == 0 ) {
                    if( readTocPmaAtip( data, 0x4, true, 0 ) ) {

                        struct atip_descriptor* atip = reinterpret_cast<struct atip_descriptor *>(data.data());

                        if( data.size() >= 11 ) {
                            inf.d->capacity = K3b::Msf( atip->lead_out_m, atip->lead_out_s, atip->lead_out_f ) - 150;
                            debugBitfield( &atip->lead_out_m, 3 );
                            qDebug() << blockDeviceName() << ": ATIP capacity: " << inf.d->capacity.toString();
                        }
                    }
                }

                //
                // for empty and appendable media capacity and usedCapacity should be filled in from
                // diskinfo above. If not they are both still 0
                //
                if( inf.d->capacity != 0 &&
                    ( inf.diskState() == STATE_EMPTY || inf.d->usedCapacity != 0 ) ) {
                    // done.
                    break;
                }
                Q_FALLTHROUGH();

            default:
            case MEDIA_CD_ROM:
                if( inf.d->capacity > 0 && inf.d->usedCapacity == 0 )
                    inf.d->usedCapacity = inf.d->capacity;

                if( inf.d->usedCapacity == 0 ) {
                    K3b::Msf readCap;
                    if( readCapacity( readCap ) ) {
                        qDebug() << "(K3b::Device::Device) READ CAPACITY: " << readCap.toString()
                                 << " other capacity: " << inf.d->capacity.toString() << Qt::endl;
                        //
                        // READ CAPACITY returns the last written sector
                        // that means the size is actually readCap + 1
                        //
                        inf.d->usedCapacity = readCap + 1;
                    }
                    else {
                        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                                 << " Falling back to readToc for capacity." << Qt::endl;
                        inf.d->usedCapacity = readToc().length();
                    }
                }
                break;

            case MEDIA_DVD_ROM: {
                K3b::Msf readCap;
                if( readCapacity( readCap ) ) {
                    qDebug() << "(K3b::Device::Device) READ CAPACITY: " << readCap.toString()
                             << " other capacity: " << inf.d->capacity.toString() << Qt::endl;
                    //
                    // READ CAPACITY returns the last written sector
                    // that means the size is actually readCap + 1
                    //
                    inf.d->usedCapacity = readCap + 1;
                }
                else {
                    //
                    // Only one track, use it's size
                    //
                    if( readTrackInformation( data, 0x1, 0x1 ) ) {
                        track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());
                        inf.d->usedCapacity = from4Byte( trackInfo->track_size );
                    }
                    else
                        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                                 << "READ TRACK INFORMATION for DVD-ROM failed." << Qt::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, 0x1, /*0xff*/ inf.numTracks()+1 ) ) {
                        track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());
                        inf.d->usedCapacity = from4Byte( trackInfo->track_start );
                        inf.d->capacity = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size );
                    }
                }
                else {
                    if( readTrackInformation( data, 0x1, inf.numTracks() ) ) {
                        track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());
                        inf.d->capacity = inf.d->usedCapacity
                                        = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size );
                    }
                }
                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, 0x1, inf.numTracks()+1 ) ) {
                    track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());
                    inf.d->usedCapacity = from4Byte( trackInfo->track_start );
                    inf.d->capacity = from4Byte( trackInfo->free_blocks ) + from4Byte( trackInfo->track_start );
                }

                //
                // 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.d->usedCapacity = readCap + 1;
                    }
                    else
                        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                                 << " READ CAPACITY for DVD-R failed." << Qt::endl;
                }

                break;

            case MEDIA_DVD_RW_OVWR:
                inf.d->numSessions = 1;
                Q_FALLTHROUGH();
            case MEDIA_DVD_RW:
            case MEDIA_DVD_RW_SEQ:
                // only one track on a DVD-RW media
                if( readTrackInformation( data, 0x1, 0x1 ) ) {
                    track_info_t* trackInfo = reinterpret_cast<track_info_t *>(data.data());
                    inf.d->capacity = from4Byte( trackInfo->track_size );
                    if( !inf.empty() ) {
                        if( readFormatCapacity( 0x10, inf.d->capacity ) )
                            qDebug() << blockDeviceName() << ": Format capacity 0x10: " << inf.d->capacity.toString();

                        inf.d->usedCapacity = from4Byte( trackInfo->track_size );
                    }
                }
                break;

            case MEDIA_DVD_PLUS_RW: {
                K3b::Msf currentMax;
                int currentMaxFormat = 0;
                if( readFormatCapacity( 0x26, inf.d->capacity, &currentMax, &currentMaxFormat ) ) {
                    if( currentMaxFormat == 0x1 ) { // unformatted or blank media
                        inf.d->usedCapacity = 0;
                        inf.d->capacity = currentMax;
                    }
                    else {
                        inf.d->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.d->capacity = currentMax;
                        }
                    }
                }
                else
                    qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                             << " READ FORMAT CAPACITIES for DVD+RW failed." << Qt::endl;

                break;
            }

            case MEDIA_BD_R:
            case MEDIA_BD_R_SRM:
            case MEDIA_BD_R_SRM_POW:
            case MEDIA_BD_R_RRM:
                //
                // get the invisible track's first sector
                // or the next writable address of the last open track
                //
                if( readDiscInformation( data ) ) {
                    int lastTrack = int( data[11]<<8 | data[6] );

                    if( readTrackInformation( data, 1, lastTrack ) ) {

                        // capacity: last track's start address + last track's size
                        inf.d->capacity = from4Byte( &data[8] ) + from4Byte( &data[24] );

                        if( data[6] & 0x80 )
                            inf.d->usedCapacity = from4Byte( &data[8] );
                        else if( data[7] & 0x1 )
                            inf.d->usedCapacity = from4Byte( &data[12] );
                        else
                            inf.d->usedCapacity = inf.d->capacity;
                    }
                }
                break;

            case MEDIA_BD_RE: {
                K3b::Msf currentMax;
                int currentMaxFormat = 0;
                if ( readFormatCapacity( 0x00, inf.d->capacity, &currentMax, &currentMaxFormat ) ) {
                    if( currentMaxFormat == 0x1 ) { // unformatted or blank media
                        inf.d->usedCapacity = 0;
                        inf.d->capacity = currentMax;
                    }
                    else {
                        inf.d->usedCapacity = currentMax;
                    }
                }
                else
                    qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                             << " READ FORMAT CAPACITIES for BD-RE failed." << Qt::endl;
                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.d->usedCapacity = readCap + 1;
                }

                break;
            }
            }
        }

        if( needToClose )
            close();
    }

    return inf;
}


K3b::Device::MediaType K3b::Device::Device::mediaType() const
{
    K3b::Device::MediaType m = MEDIA_UNKNOWN;

    if( testUnitReady() ) {

        int p = currentProfile();
        if ( p != -1 )
            m = static_cast<MediaType>(p);

        if( m & (MEDIA_UNKNOWN|MEDIA_DVD_ROM|MEDIA_HD_DVD_ROM) ) {
            //
            // We prefer 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
            UByteArray data;
            if( readDvdStructure( data ) ) {
                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:
                    qDebug() << "(K3b::Device::Device) unknown dvd media type: " << QString::number(data[4]&0xF0, 8);
                    break; // unknown
                }
            }
        }

        if( m & (MEDIA_UNKNOWN|MEDIA_BD_ROM) ) {
            //
            // We prefer the mediatype as reported by the media since this way
            // even ROM drives may report the correct type of writable media.
            //

            UByteArray data;
            if( readDiscStructure( data, 1, 0 ) ) {
                if( data.size() > 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;
                    }
                }
            }
        }

        //
        // 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) ) {
            UByteArray data;
            if( readTocPmaAtip( data, 4, false, 0 ) ) {
                if( (data[6]>>6)&1 )
                    m = MEDIA_CD_RW;
                else
                    m = MEDIA_CD_R;
            }
            else
                m = MEDIA_CD_ROM;
        }
    }

    return m;
}


bool K3b::Device::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 K3b::Device::Device::checkForJustLink()
{
    UByteArray ricoh;
    if( modeSense( ricoh, 0x30 ) ) {

        //
        // 8 byte mode header + 6 byte page data
        //

        if( ricoh.size() >= 14 ) {
            ricoh_mode_page_30* rp = reinterpret_cast<ricoh_mode_page_30 *>(ricoh.data()+8);
            d->burnfree = rp->BUEFS;
        }
    }
}


void K3b::Device::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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD Mastering";
#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 = reinterpret_cast<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 ) d->writeModes |= WRITINGMODE_SAO;
//       if( p->raw || p->raw_ms ) d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD Track At Once";
#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 = reinterpret_cast<struct cd_track_at_once_feature *>(&header[12]);
            d->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( d->writeModes & WRITINGMODE_RAW ) {
//  if( p->rw_raw ) d->writeModes |= WRITINGMODE_RAW_R96R;
//  if( p->rw_pack ) d->writeModes |= WRITINGMODE_RAW_R96P;
//       }

//       // check the data types for 1, 2, and 3 (raw16, raw96p, and raw96r)
//        debugBitfield( p->data_type, 2 );
//       if( d->writeModes & WRITINGMODE_RAW ) {
//  if( p->data_type[1] & 0x20 ) d->writeModes |= WRITINGMODE_RAW_R16;
//  if( p->data_type[1] & 0x40 ) d->writeModes |= WRITINGMODE_RAW_R96P;
//  if( p->data_type[1] & 0x80 ) d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD-RW Media Write Support";
            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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD Read (MMC5)";
            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 ) {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD Read (pre-MMC5)";
                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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+R";
            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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+RW";
#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 = reinterpret_cast<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 ) {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+RW Double Layer";
                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 ) {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+R Double Layer";
                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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "BD Read";
            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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "BD Write";
            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|MEDIA_BD_R_SRM);
                d->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 ) {
                        d->writeCapabilities |= MEDIA_BD_R_SRM_POW;
                        d->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 ) {
                        d->writeCapabilities |= MEDIA_BD_R_RRM;
                        d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD-R/-RW Write";
#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 = reinterpret_cast<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);

            d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "Rigid Restricted Overwrite";
            d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "Layer Jump Recording";
            d->writeCapabilities |= (MEDIA_DVD_R_DL|MEDIA_DVD_R_DL_JUMP);
            d->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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "HD-DVD Read";
            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 ) {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "HD-DVD Write";
            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:
                        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << " unknown profile: "
                                 << profile << Qt::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 K3b::Device::Device::checkFor2AFeatures()
{
    UByteArray mm_cap_buffer;

    if( modeSense( mm_cap_buffer, 0x2A ) ) {
        mm_cap_page_2A* mm_p = reinterpret_cast<mm_cap_page_2A *>(mm_cap_buffer.data()+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;

        d->maxReadSpeed = from2Byte(mm_p->max_read_speed);
        d->bufferSize = from2Byte( mm_p->buffer_size );
    }
    else {
        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": read mode page 2A failed!";
    }
}


void K3b::Device::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
    UByteArray buffer;

    if( !modeSense( buffer, 0x05 ) ) {
        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeSense 0x05 failed!" << Qt::endl
                 << "(K3b::Device::Device) " << blockDeviceName() << ": Cannot check write modes." << Qt::endl;
    }
    else if( buffer.size() < 18 ) { // 8 bytes header + 10 bytes used modepage
        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": Missing modepage 0x05 data." << Qt::endl
                 << "(K3b::Device::Device) " << blockDeviceName() << ": Cannot check write modes." << Qt::endl;
    }
    else {
        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": buffer.size(): " << buffer.size();

        wr_param_page_05* mp = reinterpret_cast<struct wr_param_page_05 *>(buffer.data()+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

        //    qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeselect WRITINGMODE_TAO data: ";
        //    debugBitfield( buffer.data(), buffer.size() );


        //
        // 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
        //

        qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for TAO";
        if( modeSelect( buffer, 1, 0 ) ) {
            d->writeModes |= WRITINGMODE_TAO;
            d->writeCapabilities |= MEDIA_CD_R;

            // WRITINGMODE_SAO
            mp->write_type = 0x02; // Session-at-once

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO";
            if( modeSelect( buffer, 1, 0 ) )
                d->writeModes |= WRITINGMODE_SAO;

//       mp->dbtype = 1;        // Raw data with P and Q Sub-channel (2368 bytes)
//       if( modeSelect( buffer, 1, 0 ) ) {
//  d->writeModes |= WRITINGMODE_RAW_R16;
//       }

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO_R96P";
            mp->dbtype = 2;        // Raw data with P-W Sub-channel (2448 bytes)
            if( modeSelect( buffer, 1, 0 ) ) {
                d->writeModes |= WRITINGMODE_SAO_R96P;
            }

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO_R96R";
            mp->dbtype = 3;        // Raw data with P-W raw Sub-channel (2448 bytes)
            if( modeSelect( buffer, 1, 0 ) ) {
                d->writeModes |= WRITINGMODE_SAO_R96R;
            }

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R16";
            // WRITINGMODE_RAW
            mp->write_type = 0x03; // WRITINGMODE_RAW
            mp->dbtype = 1;        // Raw data with P and Q Sub-channel (2368 bytes)
            if( modeSelect( buffer, 1, 0 ) ) {
                d->writeModes |= WRITINGMODE_RAW;
                d->writeModes |= WRITINGMODE_RAW_R16;
            }

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R96P";
            mp->dbtype = 2;        // Raw data with P-W Sub-channel (2448 bytes)
            if( modeSelect( buffer, 1, 0 ) ) {
                d->writeModes |= WRITINGMODE_RAW;
                d->writeModes |= WRITINGMODE_RAW_R96P;
            }

            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R96R";
            mp->dbtype = 3;        // Raw data with P-W raw Sub-channel (2448 bytes)
            if( modeSelect( buffer, 1, 0 ) ) {
                d->writeModes |= WRITINGMODE_RAW;
                d->writeModes |= WRITINGMODE_RAW_R96R;
            }
        }
        else {
            qDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeSelect with WRITINGMODE_TAO failed. No writer";
        }
    }

    if( needToClose )
        close();
}


int K3b::Device::Device::getMaxWriteSpeedVia2A() const
{
    int ret = 0;

    UByteArray data;

    if( modeSense( data, 0x2A ) ) {
        mm_cap_page_2A* mm = reinterpret_cast<mm_cap_page_2A *>(&data[8]);

        // MMC1 used byte 18 and 19 for the max write speed
        if( data.size() > 19 )
            ret = from2Byte( mm->max_write_speed );
    }

    return ret;
}


int K3b::Device::Device::determineMaximalWriteSpeed() const
{
    int ret = 0;

    if( mediaType() & MEDIA_CD_ALL ) {
        ret = getMaxWriteSpeedVia2A();
        if ( ret > 0 )
            return ret;
    }

    QList<int> list = determineSupportedWriteSpeeds();
    if( !list.isEmpty() ) {
        for( QList<int>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
            ret = qMax( ret, *it );
    }

    if( ret > 0 )
        return ret;
    else
        return d->maxWriteSpeed;
}


QList<int> K3b::Device::Device::determineSupportedWriteSpeeds() const
{
    QList<int> 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.
        //
        MediaType m = mediaType();
        if( m & MEDIA_CD_ALL ) {
            if( !getSupportedWriteSpeedsVia2A( ret, m ) )
                getSupportedWriteSpeedsViaGP( ret, m );

            // 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;
            UByteArray data;
            if( modeSense( data, 0x2A ) && data.size() >= 8 ) {
                const mm_cap_page_2A* mm = reinterpret_cast<const mm_cap_page_2A *>(&data.at(8));

                // MMC1 used byte 18 and 19 for the max write speed
                if( data.size() > 19 )
                    max = from2Byte( mm->max_write_speed );

                if( max > 0 ) {
                    while( !ret.isEmpty() && ret.last() > max ) {
                        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                                 << " writing speed " << ret.last() << " higher than max " << max << Qt::endl;
                        ret.pop_back();
                    }
                }
            }
        }
        else {
            if( !getSupportedWriteSpeedsViaGP( ret, m ) )
                getSupportedWriteSpeedsVia2A( ret, m );
        }

        // construct writing speeds for old devices
        if ( ret.isEmpty() && K3b::Device::isCdMedia( m ) ) {
            int max = getMaxWriteSpeedVia2A();
            for ( int i = 1; i <= max/SPEED_FACTOR_CD; i *= 2 ) {
                ret.append( i * SPEED_FACTOR_CD );
            }
        }
    }

    return ret;
}


bool K3b::Device::Device::getSupportedWriteSpeedsVia2A(QList<int>& list,
                                                    MediaType mediaType) const
{
    UByteArray/* QVarLengthArray<unsigned char> */ data;
    if (modeSense(data, 0x2A)) {
        mm_cap_page_2A* mm = reinterpret_cast<mm_cap_page_2A *>(&data[8]);

        // 8 bytes of MODE SENSE(10) header
        // 32 bytes offset of speed descriptor list in Mode page 2Ah
        // (MMC-3, table 361. Pages of MMC-1 and MMC-2 are smaller.)
        if (data.size() > 32 + 8/* replyLen */) {
            // we have descriptors
            unsigned int numDesc = from2Byte(mm->num_wr_speed_des);

            // Ensure number of descriptors claimed actually fits in the data
            // returned by the mode sense command.
            if (static_cast<int>(numDesc) > ((data.size() - 32 - 8) / 4))
                numDesc = (data.size() - 32 - 8) / 4;

            cd_wr_speed_performance* wr = reinterpret_cast<cd_wr_speed_performance *>(mm->wr_speed_des);

            qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                     << ":  Number of supported write speeds via 2A: "
                     << numDesc << Qt::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( isDvdMedia( mediaType ) && s < 1352 ) {
                    qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                             << " Invalid DVD speed: " << s << " KB/s" << Qt::endl;
                    list.clear();
                    break;
                }
                else {
                    qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                             << " : " << s << " KB/s" << Qt::endl;

                    if( isDvdMedia( mediaType ) )
                        s = fixupDvdWritingSpeed( s );

                    // sort the list
                    QList<int>::iterator it = list.begin();
                    while( it != list.end() && *it < s )
                        ++it;
                    list.insert( it, s );
                }
            }
        }
    }

    return !list.isEmpty();
}


bool K3b::Device::Device::getSupportedWriteSpeedsViaGP( QList<int>& list, MediaType mediaType ) const
{
    UByteArray data;
    if( getPerformance( data, 0x3, 0x0 ) && data.size() >= 8 ) {
        int numDesc = (data.size() - 8)/16;
        qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                 << ":  Number of supported write speeds via GET PERFORMANCE: "
                 << numDesc << Qt::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 ) ) {
//  qDebug() << "(K3b::Device::Device) " << blockDeviceName()
//         << " No write speed: " << s << " KB/s" << Qt::endl;
//  continue;
//       }

            if( isDvdMedia( mediaType ) && s < 1352 ) {
                //
                // Does this ever happen?
                //
                qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                         << " Invalid DVD speed: " << s << " KB/s" << Qt::endl;
            }
            else {
                qDebug() << "(K3b::Device::Device) " << blockDeviceName()
                         << " : " << s << " KB/s" << Qt::endl;

                if( isDvdMedia( mediaType ) )
                    s = fixupDvdWritingSpeed( s );

                QList<int>::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 );
            }
        }
    }

    return !list.isEmpty();
}


int K3b::Device::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 identified 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 {
        qDebug() << "(K3b::Device::Device::getIndex) readCd failed. Trying seek.";

        UByteArray data;
        if( seek( lba ) && readSubChannel( data, 1, 0 ) ) {
            // byte 5: 4 bits ADR (MSB) + 4 bits CONTROL (LSB)
            if( data.size() > 7 && (data[5]>>4 & 0x0F) == 0x1 ) {
                ret = data[7];
            }
            else if( seek( lba-1 ) && readSubChannel( data, 1, 0 ) ) {
                if( data.size() > 7 && (data[5]>>4 & 0x0F) == 0x1 )
                    ret = data[7];
                else
                    ret = -2;
            }
        }
        else
            qDebug() << "(K3b::Device::Device::getIndex) seek or readSubChannel failed.";
    }

    if( needToClose )
        close();

    return ret;
}


bool K3b::Device::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 ) {
            qDebug() << "(K3b::Device::Device) warning: no index != 0 found.";
        }
        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 K3b::Device::Device::indexScan( K3b::Device::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::TYPE_AUDIO ) {
            track.setIndices( QList<K3b::Msf>() );
            long index0 = -1;
            if( searchIndex0( track.firstSector().lba(), track.lastSector().lba(), index0 ) ) {
                qDebug() << "(K3b::Device::Device) found index 0: " << index0;
            }
            if( index0 > 0 )
                track.setIndex0( K3b::Msf( index0 - track.firstSector().lba() ) );
            else
                track.setIndex0( 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 K3b::Device::Device::searchIndexTransitions( long start, long end, K3b::Device::Track& track ) const
{
    qDebug() << "(K3b::Device::Device) searching for index transitions between "
             << start << " and " << end << Qt::endl;
    int startIndex = getIndex( start );
    int endIndex = getIndex( end );

    if( startIndex < 0 || endIndex < 0 ) {
        qDebug() << "(K3b::Device::Device) could not retrieve index values.";
    }
    else {
        qDebug() << "(K3b::Device::Device) indices: " << start << " - " << startIndex
                 << " and " << end << " - " << endIndex << Qt::endl;

        if( startIndex != endIndex ) {
            if( start+1 == end ) {
                QList<K3b::Msf> indices = track.indices();
                qDebug() << "(K3b::Device::Device) found index transition: " << endIndex << " " << end;
                while ( indices.count() < endIndex )
                    indices.append( K3b::Msf() );
                // we save the index relative to the first sector
                if (endIndex > 0 && endIndex < indices.size() + 1)
                    indices[endIndex - 1] = K3b::Msf(end) - track.firstSector();
                track.setIndices( indices ); // FIXME: better API
            }
            else {
                searchIndexTransitions( start, start+(end-start)/2, track );
                searchIndexTransitions( start+(end-start)/2, end, track );
            }
        }
    }
}


int K3b::Device::Device::copyrightProtectionSystemType() const
{
    UByteArray dvdheader;
    if( readDvdStructure( dvdheader, 0x1 ) ) {
        int ret = -1;
        if( dvdheader.size() >= 6 )
            ret = dvdheader[4];
        return ret;
    }
    else
        return -1;
}


bool K3b::Device::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 & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) )
            return false;

        UByteArray data;

        if( readDiscInformation( data ) ) {
            disc_info_t* inf = reinterpret_cast<disc_info_t *>(data.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;
                if (m == MEDIA_BD_R_SRM_POW)
                    nextTrack = inf->last_track_l | inf->last_track_m << 8;
                else
                    nextTrack  = inf->first_track_l | inf->first_track_m << 8;

                UByteArray trackData;

                // Read start address of the incomplete track
                if( readTrackInformation( trackData, 0x1, nextTrack ) ) {
                    if (m == MEDIA_BD_R_SRM_POW && (trackData[7] & 1))
                        nextWritableAdress = from4Byte(&trackData[12]);
                    else
                        nextWritableAdress = from4Byte( &trackData[8] );

                    if (m == MEDIA_BD_R_SRM_POW) {
                        lastSessionStart = 0;
                        success = true;
                    } else {
                        // Read start address of the first track in the last session
                        if (readTocPmaAtip(trackData, 0x1, false, 0x0)) {
                            lastSessionStart = from4Byte(&trackData[8]);
                            success = true;
                        }
                    }
                }
            }
        }
    }

    return success;
}


int K3b::Device::Device::nextWritableAddress() const
{
    UByteArray data;
    int nwa = -1;

    if( readDiscInformation( data ) ) {
        disc_info_t* inf = reinterpret_cast<disc_info_t *>(data.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;

            UByteArray trackData;

            // Read start address of the incomplete track
            if( readTrackInformation( trackData, 0x1, nextTrack ) ) {
                nwa = from4Byte( &trackData[8] );
            }

            // Read start address of the invisible track
            else if ( readTrackInformation( trackData, 0x1, 0xff ) ) {
                nwa = from4Byte( &trackData[8] );
            }
        }
    }

    return nwa;
}


QByteArray K3b::Device::Device::mediaId( int mediaType ) const
{
    QString id;

    if( mediaType & MEDIA_CD_ALL ) {
        // FIXME:
    }

    else if( mediaType & MEDIA_DVD_MINUS_ALL ) {
        UByteArray data;
        if( readDvdStructure( data, 0x0E ) ) {
            if( data[4+16] == 3 && data[4+24] == 4 ) {
                id = QString::asprintf( "%6.6s%-6.6s", data.data()+4+17, data.data()+4+25 );
            }
        }
    }

    else if( mediaType & MEDIA_DVD_PLUS_ALL ) {
        UByteArray data;
        if( readDvdStructure( data, 0x11 ) ||
            readDvdStructure( data, 0x0 ) ) {
            id = QString::asprintf( "%8.8s/%3.3s", data.data()+23, data.data()+31 );
        }
    }

    else if( mediaType & MEDIA_BD_ALL ) {
        UByteArray data;
        if( readDiscStructure( data, 1, 0 ) ) {
            if( data[4+0] == 'D' && data[4+1] == 'I' )
                id = QString::asprintf ("%6.6s/%-3.3s", data.data()+4+100, data.data()+4+106 );
        }
    }

    return id.toLatin1();
}


// int K3b::Device::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 K3b::Device::Device::usageLock() const
{
    d->mutex.lock();
}


void K3b::Device::Device::usageUnlock() const
{
    d->mutex.unlock();
}
