/*
 *
 * $Id: k3bdevicemanager.cpp 676188 2007-06-16 08:55:00Z trueg $
 * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * See the file "COPYING" for the exact licensing terms.
 */

#include <config.h>

#include "k3bdevicemanager.h"
#include "k3bdevice.h"
#include "k3bdeviceglobals.h"
#include "k3bscsicommand.h"
#include "k3bmmc.h"
#include "k3bdebug.h"

#include <tqstring.h>
#include <tqstringlist.h>
#include <tqptrlist.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqregexp.h>

#include <tdeprocess.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <tdetempfile.h>

#include <iostream>
#include <limits.h>
#include <assert.h>

#ifdef Q_OS_FREEBSD
#include <sys/param.h>
#include <sys/ucred.h>
#include <osreldate.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#ifdef HAVE_RESMGR
#include <resmgr.h>
#endif

#ifdef Q_OS_LINUX

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

#undef __STRICT_ANSI__
#include <asm/types.h>
#define __STRICT_ANSI__

#include <scsi/scsi.h>
#include <linux/major.h>


#ifndef SCSI_DISK_MAJOR
#define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \
  ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR) || \
  ((M) >= SCSI_DISK8_MAJOR && (M) <= SCSI_DISK15_MAJOR))
#endif

#ifndef SCSI_BLK_MAJOR
#define SCSI_BLK_MAJOR(M) \
  (SCSI_DISK_MAJOR(M)   \
   || (M) == SCSI_CDROM_MAJOR)
#endif

#ifndef SCSI_GENERIC_MAJOR
#define SCSI_GENERIC_MAJOR 21
#endif

#endif // Q_OS_LINUX


#ifdef Q_OS_FREEBSD
#include <cam/cam.h>
#include <cam/scsi/scsi_pass.h>
#include <camlib.h>
#endif

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



class K3bDevice::DeviceManager::Private
{
public:
  TQPtrList<K3bDevice::Device> allDevices;
  TQPtrList<K3bDevice::Device> cdReader;
  TQPtrList<K3bDevice::Device> cdWriter;
  TQPtrList<K3bDevice::Device> dvdReader;
  TQPtrList<K3bDevice::Device> dvdWriter;
  TQPtrList<K3bDevice::Device> bdReader;
  TQPtrList<K3bDevice::Device> bdWriter;

  bool checkWritingModes;
};



K3bDevice::DeviceManager::DeviceManager( TQObject* parent, const char* name )
  : TQObject( parent, name )
{
  d = new Private;
}


K3bDevice::DeviceManager::~DeviceManager()
{
  d->allDevices.setAutoDelete( true );
  delete d;
}


void K3bDevice::DeviceManager::setCheckWritingModes( bool b )
{
  d->checkWritingModes = b;
}


K3bDevice::Device* K3bDevice::DeviceManager::deviceByName( const TQString& name )
{
  return findDevice( name );
}


K3bDevice::Device* K3bDevice::DeviceManager::findDevice( int bus, int id, int lun )
{
  TQPtrListIterator<K3bDevice::Device> it( d->allDevices );
  while( it.current() )
  {
    if( it.current()->scsiBus() == bus &&
        it.current()->scsiId() == id &&
        it.current()->scsiLun() == lun )
      return it.current();

    ++it;
  }

  return 0;
}


K3bDevice::Device* K3bDevice::DeviceManager::findDevice( const TQString& devicename )
{
  if( devicename.isEmpty() ) {
    k3bDebug() << "(K3bDevice::DeviceManager) request for empty device!" << endl;
    return 0;
  }
  TQPtrListIterator<K3bDevice::Device> it( d->allDevices );
  while( it.current() ) {
    if( it.current()->deviceNodes().contains(devicename) )
      return it.current();

    ++it;
  }

  return 0;
}


const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::cdWriter() const
{
  return d->cdWriter;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::cdReader() const
{
  return d->cdReader;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::dvdWriter() const
{
  return d->dvdWriter;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::dvdReader() const
{
  return d->dvdReader;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::blueRayReader() const
{
  return d->bdReader;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::blueRayWriters() const
{
  return d->bdWriter;
}

const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::burningDevices() const
{
  return cdWriter();
}


const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::readingDevices() const
{
  return cdReader();
}


const TQPtrList<K3bDevice::Device>& K3bDevice::DeviceManager::allDevices() const
{
  return d->allDevices;
}


int K3bDevice::DeviceManager::scanBus()
{
  unsigned int numDevs = d->allDevices.count();

#ifdef Q_OS_LINUX
  LinuxDeviceScan();
#endif
#ifdef Q_OS_FREEBSD
  BSDDeviceScan();
#endif
#ifdef Q_OS_NETBSD
  NetBSDDeviceScan();
#endif

  return d->allDevices.count() - numDevs;
}


void K3bDevice::DeviceManager::LinuxDeviceScan()
{
#ifdef HAVE_RESMGR
  //
  // Resmgr device scan
  //
  char** resmgrDevices = rsm_list_devices( 0 );
  if( resmgrDevices ) {
    for( int i = 0; resmgrDevices[i]; ++i ) {
      addDevice( resmgrDevices[i] );
      free( resmgrDevices[i] );
    }

    free( resmgrDevices );
  }
#endif

  TQFile info("/proc/sys/dev/cdrom/info");
  TQString line,devstring;
  if( info.open(IO_ReadOnly) ) {
    info.readLine(line,80); // CD-ROM information, Id: cdrom.c 3.12 2000/10/18
    info.readLine(line,80); //

    TQRegExp re("[\t\n:]+");
    while( info.readLine( line, 80 ) > 0 ) {
      if( line.contains("drive name") > 0 ) {
	int i = 1;
	TQString dev;
	while( !(dev = line.section(re, i, i)).isEmpty() ) {
	  if( addDevice( TQString("/dev/%1").arg(dev) ) ) {
	    devstring += dev + "|";
	  }
	  // according to the LINUX ALLOCATED DEVICES document (http://www.lanana.org/docs/device-list/),
	  // the official device names for SCSI-CDROM's (block major 11) are /dev/sr*, the
	  // prefix /dev/scd instead of /dev/sr has been used as well, and might make more sense.
	  // Since there should be one and only one device node (and maybe several symbolic links) for
	  // each physical device the next line should be better
	  //      else if ( dev.startsWith("sr") )
	  if ( dev.startsWith("sr") ) {
	    if( addDevice(TQString("/dev/%1").arg(dev.replace(TQRegExp("r"),"cd"))) )
	      devstring += dev + "|";
	  }
	  ++i;
	}
      }
      break;
    }
    info.close();
  }
  else {
    kdError() << "(K3bDevice::DeviceManager) could not open /proc/sys/dev/cdrom/info" << endl;
  }

  //
  // Scan the generic devices if we have scsi devices
  //
  k3bDebug() << "(K3bDevice::DeviceManager) SCANNING FOR GENERIC DEVICES." << endl;
  for( int i = 0; i < 16; i++ ) {
    TQString sgDev = resolveSymLink( TQString("/dev/sg%1").arg(i) );
    int bus = -1, id = -1, lun = -1;
    if( determineBusIdLun( sgDev, bus, id, lun ) ) {
      if( Device* dev = findDevice( bus, id, lun ) ) {
	dev->m_genericDevice = sgDev;
      }
    }
  }
  // FIXME: also scan /dev/scsi/hostX.... for devfs without symlinks
}


void K3bDevice::DeviceManager::NetBSDDeviceScan()
{
  // Generate entries for /dev/cd* devices
  // Note: As there are only 10 possible /dev/(r)cd devices,
  // only these will be found.

  int   i;

  // Whole disk mask (According to cd(4), the AMD64, i386 and BeBox ports use
  // 'd' as whole-disk partition, the rest uses 'c'.)

#if defined(__i386__) || defined (__amd64__) || defined (__bebox__)
  static const char slicename = 'd';
#else
  static const char slicename = 'c';
#endif

  char devicename[11]; // /dev/rcdXd + trailing zero

  for (i = 0; i < 10; i++ ) // cd(4) claims there are max. 10 CD devices.
  {
    snprintf(devicename,11,"/dev/rcd%d%c",i, slicename);
    addDevice(TQString(devicename));
  }
}


void K3bDevice::DeviceManager::BSDDeviceScan()
{
  // Unfortunately uses lots of FBSD-specific data structures
#ifndef Q_OS_FREEBSD
  // bool bsdspecificcode = false;
  // assert(bsdspecificcode);
#endif

#ifdef Q_OS_FREEBSD
  union ccb ccb;
  int fd;
  int need_close = 0;
  int skip_device = 0;
  int bus, target, lun;
  TQString dev1, dev2;

  if ((fd = open(XPT_DEVICE, O_RDWR)) == -1)
    {
      k3bDebug() << "couldn't open %s " << XPT_DEVICE << endl;
      return;
    }

  memset(&ccb, 0, sizeof(ccb));

  ccb.ccb_h.func_code = XPT_DEV_MATCH;
  char buffer[100*sizeof(struct dev_match_result)];
  ccb.cdm.match_buf_len = 100*sizeof(struct dev_match_result);
  ccb.cdm.matches = (struct dev_match_result *)buffer;
  ccb.cdm.num_matches = 0;
  ccb.cdm.num_patterns = 0;
  ccb.cdm.pattern_buf_len = 0;
  do {
    if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
      k3bDebug() << "(BSDDeviceScan) error sending CAMIOCOMMAND ioctl: " << errno << endl;
      break;
    }

    if ((ccb.ccb_h.status != CAM_REQ_CMP)
	|| ((ccb.cdm.status != CAM_DEV_MATCH_LAST) && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
      k3bDebug() << "(BSDDeviceScan) got CAM error " << ccb.ccb_h.status << ", CDM error %d" << ccb.cdm.status << endl;
      break;
    }
    k3bDebug() << "(BSDDeviceScan) number of matches " << (int)ccb.cdm.num_matches << endl;
    for (int i = 0; i < (int)ccb.cdm.num_matches; i++) {
      switch (ccb.cdm.matches[i].type) {
      case DEV_MATCH_DEVICE: {
	struct device_match_result *dev_result = &ccb.cdm.matches[i].result.device_result;

	if (dev_result->flags & DEV_RESULT_UNCONFIGURED)
	  {
	    skip_device = 1;
	    break;
	  }
	else
	  skip_device = 0;
	if (need_close)
	  {
	    TQString pass = dev1;
	    TQString dev = "/dev/" + dev2;
	    if (dev2.startsWith("pass"))
	      {
		pass = dev2;
		dev = "/dev/" + dev1;
	      }
#if __FreeBSD_version < 500100
	    dev += "c";
#endif
	    if (!dev1.isEmpty() && !dev2.isEmpty() && dev.startsWith("/dev/cd"))
	    {
	      Device* device = new Device(dev.latin1());
	      device->m_bus = bus;
	      device->m_target = target;
	      device->m_lun = lun;
	      device->m_passDevice = "/dev/" + pass;
	      k3bDebug() << "(BSDDeviceScan) add device " << dev << ":" << bus << ":" << target << ":" << lun << endl;
	      addDevice(device);
	    }
	    need_close = 0;
	    dev1="";
	    dev2="";
	  }
	bus = dev_result->path_id;
	target = dev_result->target_id;
	lun = dev_result->target_lun;

	need_close = 1;

	break;
      }
      case DEV_MATCH_PERIPH: {
	struct periph_match_result *periph_result = &ccb.cdm.matches[i].result.periph_result;

	if (skip_device != 0)
	  break;

	if (need_close > 1)
	  dev1 = periph_result->periph_name + TQString::number(periph_result->unit_number);
	else
	  dev2 = periph_result->periph_name + TQString::number(periph_result->unit_number);

	need_close++;
	break;
      }
      case DEV_MATCH_BUS : {
	// bool cannotmatchbus = false;
	// assert(cannotmatchbus);
	break;
      }
      }
    }

  } while ((ccb.ccb_h.status == CAM_REQ_CMP)
	   && (ccb.cdm.status == CAM_DEV_MATCH_MORE));

  if (need_close)
    {
      TQString pass = dev1;
      TQString dev = "/dev/" + dev2;
      if (dev2.startsWith("pass"))
	{
	  pass = dev2;
	  dev = "/dev/" + dev1;
	}
#if __FreeBSD_version < 500100
      dev += "c";
#endif
      if (!dev1.isEmpty() && !dev2.isEmpty() && dev.startsWith("/dev/cd"))
      {
        Device* device = new Device(dev.latin1());
        device->m_bus = bus;
        device->m_target = target;
        device->m_lun = lun;
        device->m_passDevice = "/dev/" + pass;
        k3bDebug() << "(BSDDeviceScan) add device " << dev << ":" << bus << ":" << target << ":" << lun << endl;
        addDevice(device);
      }
    }
  close(fd);
#endif
}


void K3bDevice::DeviceManager::printDevices()
{
  k3bDebug() << "Devices:" << endl
	    << "------------------------------" << endl;
  TQPtrListIterator<Device> it( allDevices() );
  for( ; *it; ++it ) {
    Device* dev = *it;
    k3bDebug() << "Blockdevice:    " << dev->blockDeviceName() << endl
	      << "Generic device: " << dev->genericDevice() << endl
	      << "Vendor:         " << dev->vendor() << endl
	      << "Description:    " << dev->description() << endl
	      << "Version:        " << dev->version() << endl
	      << "Write speed:    " << dev->maxWriteSpeed() << endl
	      << "Profiles:       " << mediaTypeString( dev->supportedProfiles() ) << endl
	      << "Read Cap:       " << mediaTypeString( dev->readCapabilities() ) << endl
	      << "Write Cap:      " << mediaTypeString( dev->writeCapabilities() ) << endl
	      << "Writing modes:  " << writingModeString( dev->writingModes() ) << endl
	      << "Reader aliases: " << dev->deviceNodes().join(", ") << endl
	      << "------------------------------" << endl;
  }
}


void K3bDevice::DeviceManager::clear()
{
  // clear current devices
  d->cdReader.clear();
  d->cdWriter.clear();
  d->dvdReader.clear();
  d->dvdWriter.clear();
  d->bdReader.clear();
  d->bdWriter.clear();

  // to make sure no one crashes lets keep the devices around until the changed
  // signals return
  TQPtrList<K3bDevice::Device> tmp = d->allDevices;
  tmp.setAutoDelete( true );

  d->allDevices.clear();

  emit changed( this );
  emit changed();
}


bool K3bDevice::DeviceManager::readConfig( TDEConfig* c )
{
  //
  // New configuration format since K3b 0.11.94
  // for details see saveConfig()
  //

  if( !c->hasGroup( "Devices" ) )
    return false;

  c->setGroup( "Devices" );

  TQStringList deviceSearchPath = c->readListEntry( "device_search_path" );
  for( TQStringList::const_iterator it = deviceSearchPath.constBegin();
       it != deviceSearchPath.constEnd(); ++it )
    addDevice( *it );

  //
  // Iterate over all devices and check if we have a config entry
  //
  for( TQPtrListIterator<K3bDevice::Device> it( d->allDevices ); *it; ++it ) {
    K3bDevice::Device* dev = *it;

    TQString configEntryName = dev->vendor() + " " + dev->description();
    TQStringList list = c->readListEntry( configEntryName );
    if( !list.isEmpty() ) {
      k3bDebug() << "(K3bDevice::DeviceManager) found config entry for devicetype: " << configEntryName << endl;

      dev->setMaxReadSpeed( list[0].toInt() );
      if( list.count() > 1 )
	dev->setMaxWriteSpeed( list[1].toInt() );
      if( list.count() > 2 )
	dev->setCdrdaoDriver( list[2] );
      if( list.count() > 3 )
	dev->setCdTextCapability( list[3] == "yes" );
    }
  }

  return true;
}


bool K3bDevice::DeviceManager::saveConfig( TDEConfig* c )
{
  //
  // New configuration format since K3b 0.11.94
  //
  // We save a device search path which contains all device nodes
  // where devices could be found including the old search path.
  // This way also for example a manually added USB device will be
  // found between sessions.
  // Then we do not save the device settings (writing speed, cdrdao driver)
  // for every single device but for every device type.
  // This also makes sure device settings are kept between sessions
  //

  c->setGroup( "Devices" );
  TQStringList deviceSearchPath = c->readListEntry( "device_search_path" );
  // remove duplicate entries (caused by buggy old implementations)
  TQStringList saveDeviceSearchPath;
  for( TQStringList::const_iterator it = deviceSearchPath.constBegin(); it != deviceSearchPath.constEnd(); ++it )
    if( !saveDeviceSearchPath.contains( *it ) )
      saveDeviceSearchPath.append( *it );

  for( TQPtrListIterator<K3bDevice::Device> it( d->allDevices ); *it; ++it ) {
    K3bDevice::Device* dev = *it;

    // update device search path
    if( !saveDeviceSearchPath.contains( dev->blockDeviceName() ) )
      saveDeviceSearchPath.append( dev->blockDeviceName() );

    // save the device type settings
    TQString configEntryName = dev->vendor() + " " + dev->description();
    TQStringList list;
    list << TQString::number(dev->maxReadSpeed())
	 << TQString::number(dev->maxWriteSpeed())
	 << dev->cdrdaoDriver();

    if( dev->cdrdaoDriver() != "auto" )
      list << ( dev->cdTextCapable() == 1 ? "yes" : "no" );
    else
      list << "auto";

    c->writeEntry( configEntryName, list );
  }

  c->writeEntry( "device_search_path", saveDeviceSearchPath );

  c->sync();

  return true;
}


bool K3bDevice::DeviceManager::testForCdrom( const TQString& devicename )
{
#ifdef Q_OS_FREEBSD
  Q_UNUSED(devicename);
  return true;
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD)
  bool ret = false;
  int cdromfd = K3bDevice::openDevice( devicename.ascii() );
  if (cdromfd < 0) {
    k3bDebug() << "could not open device " << devicename << " (" << strerror(errno) << ")" << endl;
    return ret;
  }

  // stat the device
  struct stat cdromStat;
  if( ::fstat( cdromfd, &cdromStat ) )
    return false;

  if( !S_ISBLK( cdromStat.st_mode) ) {
    k3bDebug() << devicename << " is no block device" << endl;
  }
  else {
    k3bDebug() << devicename << " is block device (" << (int)(cdromStat.st_rdev & 0xFF) << ")" << endl;
#if defined(Q_OS_NETBSD)
  }
  {
#endif
    // inquiry
    // use a 36 bytes buffer since not all devices return the full inquiry struct
    unsigned char buf[36];
    struct inquiry* inq = (struct inquiry*)buf;
    ::memset( buf, 0, sizeof(buf) );

    ScsiCommand cmd( cdromfd );
    cmd[0] = MMC_INQUIRY;
    cmd[4] = sizeof(buf);
    cmd[5] = 0;

    if( cmd.transport( TR_DIR_READ, buf, sizeof(buf) ) ) {
      k3bDebug() << "(K3bDevice::Device) Unable to do inquiry. " << devicename << " is not a cdrom device" << endl;
    }
    else if( (inq->p_device_type&0x1f) != 0x5 ) {
      k3bDebug() << devicename << " seems not to be a cdrom device: " << strerror(errno) << endl;
    }
    else {
      ret = true;
      k3bDebug() << devicename << " seems to be cdrom" << endl;
    }
  }

  ::close( cdromfd );
  return ret;
#endif
}

K3bDevice::Device* K3bDevice::DeviceManager::addDevice( const TQString& devicename )
{
#ifdef Q_OS_FREEBSD
  return 0;
#endif

  K3bDevice::Device* device = 0;

  // resolve all symlinks
  TQString resolved = resolveSymLink( devicename );
  k3bDebug() << devicename << " resolved to " << resolved << endl;

  if ( K3bDevice::Device* oldDev = findDevice(resolved) ) {
    k3bDebug() << "(K3bDevice::DeviceManager) dev " << resolved  << " already found" << endl;
    oldDev->addDeviceNode( devicename );
    return 0;
  }

  if( !testForCdrom(resolved) ) {
#ifdef HAVE_RESMGR
    // With resmgr we might only be able to open the symlink name.
    if( testForCdrom(devicename) ) {
      resolved = devicename;
    }
    else {
      return 0;
    }
#else
    return 0;
#endif
  }

  int bus = -1, target = -1, lun = -1;
  bool scsi = determineBusIdLun( resolved, bus, target, lun );
  if(scsi) {
    if ( K3bDevice::Device* oldDev = findDevice(bus, target, lun) ) {
      k3bDebug() << "(K3bDevice::DeviceManager) dev " << resolved  << " already found" << endl;
      oldDev->addDeviceNode( devicename );
      return 0;
    }
  }

  device = new K3bDevice::Device(resolved);
  if( scsi ) {
    device->m_bus = bus;
    device->m_target = target;
    device->m_lun = lun;
  }

  return addDevice(device);
}


K3bDevice::Device* K3bDevice::DeviceManager::addDevice( K3bDevice::Device* device )
{
  const TQString devicename = device->devicename();

  if( !device->init() ) {
    k3bDebug() << "Could not initialize device " << devicename << endl;
    delete device;
    return 0;
  }

  if( device ) {
    d->allDevices.append( device );

    // not every drive is able to read CDs
    // there are some 1st generation DVD writer that cannot
    if( device->type() & K3bDevice::DEVICE_CD_ROM )
      d->cdReader.append( device );
    if( device->readsDvd() )
      d->dvdReader.append( device );
    if( device->writesCd() )
      d->cdWriter.append( device );
    if( device->writesDvd() )
      d->dvdWriter.append( device );
    if( device->readCapabilities() & MEDIA_BD_ALL )
      d->bdReader.append( device );
    if( device->writeCapabilities() & MEDIA_BD_ALL )
      d->bdWriter.append( device );

    if( device->writesCd() ) {
      // default to max write speed
      k3bDebug() << "(K3bDevice::DeviceManager) setting current write speed of device "
		<< device->blockDeviceName()
		<< " to " << device->maxWriteSpeed() << endl;
      device->setCurrentWriteSpeed( device->maxWriteSpeed() );
    }

    emit changed( this );
    emit changed();
  }

  return device;
}


void K3bDevice::DeviceManager::removeDevice( const TQString& dev )
{
  if( Device* device = findDevice( dev ) ) {
    d->cdReader.removeRef( device );
    d->dvdReader.removeRef( device );
    d->bdReader.removeRef( device );
    d->cdWriter.removeRef( device );
    d->dvdWriter.removeRef( device );
    d->bdWriter.removeRef( device );
    d->allDevices.removeRef( device );

    emit changed( this );
    emit changed();

    delete device;
  }
}


bool K3bDevice::DeviceManager::determineBusIdLun( const TQString& dev, int& bus, int& id, int& lun )
{
#ifdef Q_OS_FREEBSD
  Q_UNUSED(dev);
  Q_UNUSED(bus);
  Q_UNUSED(id);
  Q_UNUSED(lun);
  return false;
  /* NOTREACHED */
#endif

#ifdef Q_OS_NETBSD
  int cdromfd = K3bDevice::openDevice ( dev.ascii() );
  if (cdromfd < 0) {
    int local_errno = errno; // For all we know, k3bDebug() destroys errno
    k3bDebug() << "could not open device " << dev << " (" << strerror(local_errno) << ")" << endl;
    return false;
  }

  struct scsi_addr my_addr;

  if (::ioctl(cdromfd, SCIOCIDENTIFY, &my_addr))
  {
    int local_errno = errno; // For all we know, k3bDebug() destroys errno
    k3bDebug() << "ioctl(SCIOCIDENTIFY) failed on device " << dev << " (" << strerror(local_errno) << ")" << endl;

    ::close(cdromfd);
    return false;
  }

  if (my_addr.type == TYPE_ATAPI)
  {
    // XXX Re-map atapibus, so it doesn't conflict with "real" scsi
    // busses

    bus = 15;
    id  = my_addr.addr.atapi.drive + 2 * my_addr.addr.atapi.atbus;
    lun = 0;
  }
  else
  {
    bus = my_addr.addr.scsi.scbus;
    id  = my_addr.addr.scsi.target;
    lun = my_addr.addr.scsi.lun;
  }

  ::close(cdromfd);

  return true;
#endif

#ifdef Q_OS_LINUX
  int ret = false;
  int cdromfd = K3bDevice::openDevice( dev.ascii() );
  if (cdromfd < 0) {
    return false;
  }

  struct stat cdromStat;
  if ( ::fstat( cdromfd, &cdromStat ) )
      return false;

  if( SCSI_BLK_MAJOR( cdromStat.st_rdev>>8 ) ||
      SCSI_GENERIC_MAJOR == (cdromStat.st_rdev>>8) ) {
    struct ScsiIdLun
    {
      int id;
      int lun;
    };
    ScsiIdLun idLun;

    // in kernel 2.2 SCSI_IOCTL_GET_IDLUN does not contain the bus id
    if ( (::ioctl( cdromfd, SCSI_IOCTL_GET_IDLUN, &idLun ) < 0) ||
         (::ioctl( cdromfd, SCSI_IOCTL_GET_BUS_NUMBER, &bus ) < 0) ) {
      k3bDebug() << "Need a filename that resolves to a SCSI device" << endl;
      ret = false;
    }
    else {
      id  = idLun.id & 0xff;
      lun = (idLun.id >> 8) & 0xff;
      k3bDebug() << "bus: " << bus << ", id: " << id << ", lun: " << lun << endl;
      ret = true;
    }
  }

  ::close(cdromfd);
  return ret;
#endif
}


TQString K3bDevice::DeviceManager::resolveSymLink( const TQString& path )
{
  char resolved[PATH_MAX];
  if( !realpath( TQFile::encodeName(path), resolved ) )
  {
    k3bDebug() << "Could not resolve " << path << endl;
    return path;
  }

  return TQString::fromLatin1( resolved );
}


#include "k3bdevicemanager.moc"