Logo Search packages:      
Sourcecode: qpxtool version File versions  Download package

qpx_transport.cpp

//
// This is part of dvd+rw-tools by Andy Polyakov <appro@fy.chalmers.se>
//
// Use-it-on-your-own-risk, GPL bless...
//
// For further details see http://fy.chalmers.se/~appro/linux/DVD+RW/
//


//
// edited by ShultZ to use with QPxTool http://qpxtool.sf.net
//

int sense2str(int err, char* str);

#if defined(__unix) || defined(__unix__)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/time.h>

#include "qpx_transport.h"


long getmsecs()
{ struct timeval tv;
    gettimeofday (&tv,NULL);
  return tv.tv_sec*1000+tv.tv_usec/1000;
}

long getusecs()
{ struct timeval tv;
    gettimeofday (&tv,NULL);
  return tv.tv_sec*1000000+tv.tv_usec;
}

#include <errno.h>

#ifndef EMEDIUMTYPE
#define EMEDIUMTYPE     EINVAL
#endif
#ifndef     ENOMEDIUM
#define     ENOMEDIUM   ENODEV
#endif

//*

#elif defined(_WIN32)
#include <windows.h>
#include <stdio.h>

#define EINVAL          ERROR_BAD_ARGUMENTS
#define ENOMEM          ERROR_OUTOFMEMORY
#define EMEDIUMTYPE     ERROR_MEDIA_INCOMPATIBLE
#define ENOMEDIUM ERROR_MEDIA_OFFLINE
#define ENODEV          ERROR_BAD_COMMAND
#define EAGAIN          ERROR_NOT_READY
#define ENOSPC          ERROR_DISK_FULL
#define EIO       ERROR_NOT_SUPPORTED
#define ENXIO           ERROR_GEN_FAILURE

static class _win32_errno {
    public:
      operator int()          { return GetLastError(); }
      int operator=(int e)    { SetLastError(e); return e; }
} _sys_errno;
#ifdef errno
#undef errno

#endif
#define errno _sys_errno

inline void perror (const char *str)
{ LPVOID lpMsgBuf;

    FormatMessage( 
      FORMAT_MESSAGE_ALLOCATE_BUFFER |
      FORMAT_MESSAGE_FROM_SYSTEM | 
      FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL,
      GetLastError(),
      0, // Default language
      (LPTSTR) &lpMsgBuf,
      0,
      NULL 
      );
    if (str)
      fprintf (stderr,"%s: %s",str,lpMsgBuf);
    else
      fprintf (stderr,"%s",lpMsgBuf);

    LocalFree(lpMsgBuf);
}

#define poll(a,b,t)     Sleep(t)
#define getmsecs()      GetTickCount()
#define exit(e)         ExitProcess(e)
//*/
#endif

#define CREAM_ON_ERRNO_NAKED(s)                       \
    switch ((s)[12])                            \
    { case 0x04:  errno=EAGAIN;     break;            \
      case 0x20:  errno=ENODEV;     break;            \
      case 0x21:  if ((s)[13]==0)   errno=ENOSPC;     \
                  else        errno=EINVAL;     \
                  break;                        \
      case 0x30:  errno=EMEDIUMTYPE;      break;      \
      case 0x3A:  errno=ENOMEDIUM;  break;      \
    }
#define CREAM_ON_ERRNO(s)     do { CREAM_ON_ERRNO_NAKED(s) } while(0)

#define     FATAL_START(er)   (0x80|(er))
#define ERRCODE(s)      ((((s)[2]&0x0F)<<16)|((s)[12]<<8)|((s)[13]))
#define     SK(errcode) (((errcode)>>16)&0xF)
#define     ASC(errcode)      (((errcode)>>8)&0xFF)
#define ASCQ(errcode)   ((errcode)&0xFF)

void sperror (const char *cmd,int err) //,  Scsi_Command *scsi)
{
      int saved_errno=errno;
      char sense_str[255];
      sense2str(err, sense_str);

      if (err==-1) {
            fprintf (stderr,":-( unable to %s : ", cmd);
            errno=saved_errno, perror (NULL);
      } else
            fprintf (stderr,":-[ %s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh]: %s\n",
                  cmd, SK(err), ASC(err), ASCQ(err),sense_str);
}

autofree::autofree()
      { ptr=NULL; }
autofree::~autofree()
      { if (ptr) free(ptr); }

#if defined(__linux)

#include <sys/ioctl.h>
#include <linux/cdrom.h>
#include <mntent.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <scsi/sg.h>
#if !defined(SG_FLAG_LUN_INHIBIT)
# if defined(SG_FLAG_UNUSED_LUN_INHIBIT)
#  define SG_FLAG_LUN_INHIBIT SG_FLAG_UNUSED_LUN_INHIBIT
# else
#  define SG_FLAG_LUN_INHIBIT 0
# endif
#endif
#ifndef CHECK_CONDITION
#define CHECK_CONDITION 0x01
#endif

#ifdef SG_IO

USE_SG_IO::USE_SG_IO()
{ struct utsname buf;
      uname (&buf);
      // was CDROM_SEND_PACKET declared dead in 2.5?
      yes_or_no=(strcmp(buf.release,"2.5.43")>=0);
}

USE_SG_IO::~USE_SG_IO(){}

#endif
Scsi_Command::Scsi_Command()        { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command::Scsi_Command(int f)   { fd=f,  autoclose=0; filename=NULL; }
Scsi_Command::Scsi_Command(void*f)  { fd=(long)f, autoclose=0; filename=NULL; }
Scsi_Command::~Scsi_Command()
{
      if (fd>=0 && autoclose) close(fd),fd=-1;
      if (filename) free(filename),filename=NULL;
}

int Scsi_Command::associate (const char *file,const struct stat *ref=NULL)
{
      struct stat sb;
      /*
       * O_RDWR is expected to provide for none set-root-uid
       * execution under Linux kernel 2.6[.8]. Under 2.4 it
       * falls down to O_RDONLY...
       */
      if ((fd=open (file,O_RDWR|O_NONBLOCK)) < 0 &&
        (fd=open (file,O_RDONLY|O_NONBLOCK)) < 0) return 0;
      if (fstat(fd,&sb) < 0) return 0;
      if (!S_ISBLK(sb.st_mode)) { errno=ENOTBLK;return 0; }
      if (ref && (!S_ISBLK(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
            { errno=ENXIO; return 0; }
      filename=strdup(file);
      return 1;
}
void Scsi_Command::timeout(int i) { cgc.timeout=sg_io.timeout=i*1000; }
#ifdef SG_IO
size_t Scsi_Command::residue() { return use_sg_io?sg_io.resid:0; }
#else
size_t Scsi_Command::residue() { return 0; }
#endif
int Scsi_Command::transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{
      int ret = 0;
#ifdef SG_IO
#define KERNEL_BROKEN 0
      if (use_sg_io)
      {
            sg_io.dxferp            = buf;
            sg_io.dxfer_len         = sz;
            sg_io.dxfer_direction   = use_sg_io[dir];
            if (ioctl (fd,SG_IO,&sg_io)) return -1;
#if !KERNEL_BROKEN
            if ((sg_io.info&SG_INFO_OK_MASK) != SG_INFO_OK)
#else
            if (sg_io.status)
#endif
            {
                  errno=EIO; ret=-1;
#if !KERNEL_BROKEN
                  if (sg_io.masked_status&CHECK_CONDITION)
#endif
                  {
                        ret = ERRCODE(sg_io.sbp);
                        if (ret==0) ret=-1;
                  else
                        CREAM_ON_ERRNO(sg_io.sbp);
                  }
            }
            return ret;
      }
      else
#undef KERNEL_BROKEN
#endif
      {
            cgc.buffer        = (unsigned char *)buf;
            cgc.buflen        = sz;
            cgc.data_direction      = dir;
            if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
            {
                  ret = ERRCODE(_sense.u);
                  if (ret==0) ret=-1;
            }
      }
      return ret;
}

int Scsi_Command::umount(int f=-1)
{
      struct stat    fsb,msb;
      struct mntent *mb;
      FILE          *fp;
      pid_t          pid,rpid;
      int            ret=0,rval;

      if (f==-1) f=fd;
      if (fstat (f,&fsb) < 0)                   return -1;
      if ((fp=setmntent ("/proc/mounts","r"))==NULL)  return -1;

      while ((mb=getmntent (fp))!=NULL)
      {
            if (stat (mb->mnt_fsname,&msb) < 0) continue; // corrupted line?
            if (msb.st_rdev == fsb.st_rdev)
            {
                  ret = -1;
                  if ((pid = fork()) == (pid_t)-1) break;
                  if (pid == 0) execl ("/bin/umount","umount",mb->mnt_dir,NULL);
                  while (1)
                  {
                        rpid = waitpid (pid,&rval,0);
                        if (rpid == (pid_t)-1) 
                        {
                              if (errno==EINTR) continue;
                              else break;
                        }
                        else if (rpid != pid)
                        {
                              errno = ECHILD;
                              break;
                        }
                        if (WIFEXITED(rval))
                        {
                              if (WEXITSTATUS(rval) == 0) ret=0;
                              else errno=EBUSY; // most likely
                              break;
                        }
                        else
                        {
                              errno = ENOLINK; // some phony errno
                              break;
                        }
                  }
            break;
            }
      }
      endmntent (fp);
      return ret;
}
int Scsi_Command::is_reload_needed ()
{ return ioctl (fd,CDROM_MEDIA_CHANGED,CDSL_CURRENT) == 0; }

#elif defined(__OpenBSD__) || defined(__NetBSD__)

#include <sys/ioctl.h>
#include <sys/scsiio.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>

typedef off_t off64_t;
#define stat64   stat
#define fstat64  fstat
#define open64   open
#define pread64    pread
#define pwrite64 pwrite
#define lseek64  lseek

    Scsi_Command::Scsi_Command()    { fd=-1, autoclose=1; filename=NULL; }
    Scsi_Command::Scsi_Command(int f)     { fd=f,  autoclose=0; filename=NULL; }
    Scsi_Command::Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
    Scsi_Command::~Scsi_Command()   { if (fd>=0 && autoclose) close(fd),fd=-1;
                    if (filename) free(filename),filename=NULL;
                  }
    int Scsi_Command::associate (const char *file,const struct stat *ref=NULL)
    { struct stat sb;

      fd=open(file,O_RDWR|O_NONBLOCK);
      // this is --^^^^^^-- why we have to run set-root-uid...

      if (fd < 0)                         return 0;
      if (fstat(fd,&sb) < 0)                    return 0;
      if (!S_ISCHR(sb.st_mode))     { errno=EINVAL; return 0; }

      if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
      {   errno=ENXIO; return 0;   }

      filename=strdup(file);

      return 1;
    }
    void Scsi_Command::timeout(int i)                 { req.timeout=i*1000; }
    size_t Scsi_Command::residue()              { return req.datalen-req.datalen_used; }
    int Scsi_Command::transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
    { int ret=0;

      req.databuf = (caddr_t)buf;
      req.datalen = sz;
      req.flags |= dir;
      if (ioctl (fd,SCIOCCOMMAND,&req) < 0)     return -1;
      if (req.retsts==SCCMD_OK)           return 0;

      errno=EIO; ret=-1;
      if (req.retsts==SCCMD_SENSE)
      {   ret = ERRCODE(req.sense);
          if (ret==0) ret=-1;
          else    CREAM_ON_ERRNO(req.sense);
      }
      return ret;
    }
    // this code is basically redundant... indeed, we normally want to
    // open device O_RDWR, but we can't do that as long as it's mounted.
    // in other words, whenever this routine is invoked, device is not
    // mounted, so that it could as well just return 0;
    int Scsi_Command::umount(int f=-1)
    { struct stat    fsb,msb;
      struct statfs *mntbuf;
      int            ret=0,mntsize,i;

      if (f==-1) f=fd;

      if (fstat (f,&fsb) < 0)                   return -1;
      if ((mntsize=getmntinfo(&mntbuf,MNT_NOWAIT))==0)return -1;

      for (i=0;i<mntsize;i++)
      { char rdev[MNAMELEN+1],*slash,*rslash;

          mntbuf[i].f_mntfromname[MNAMELEN-1]='\0';   // paranoia
          if ((slash=strrchr (mntbuf[i].f_mntfromname,'/'))==NULL) continue;
          strcpy (rdev,mntbuf[i].f_mntfromname); // rdev is 1 byte larger!
          rslash = strrchr  (rdev,'/');
          *(rslash+1) = 'r', strcpy (rslash+2,slash+1);
          if (stat (rdev,&msb) < 0) continue;
          if (msb.st_rdev == fsb.st_rdev)
          { ret=unmount (mntbuf[i].f_mntonname,0);
            break;
            }
      }

      return ret;
    }
    int Scsi_Command::is_reload_needed ()
    { return 1;   }

#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)

#include <sys/ioctl.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <dirent.h>

typedef off_t off64_t;
#define stat64   stat
#define fstat64  fstat
#define open64   open
#define pread64  pread
#define pwrite64 pwrite
#define lseek64  lseek

#define ioctl_fd (((struct cam_device *)ioctl_handle)->fd)

    Scsi_Command::Scsi_Command()
    { cam=NULL, fd=-1, autoclose=1; filename=NULL;   }
    Scsi_Command::Scsi_Command(int f)
    { char pass[32];    // periph_name is 16 chars long

      cam=NULL, fd=-1, autoclose=1, filename=NULL;

      memset (&ccb,0,sizeof(ccb));
      ccb.ccb_h.func_code = XPT_GDEVLIST;
      if (ioctl (f,CAMGETPASSTHRU,&ccb) < 0) return;

      sprintf (pass,"/dev/%.15s%u",ccb.cgdl.periph_name,ccb.cgdl.unit_number);
      cam=cam_open_pass (pass,O_RDWR,NULL);
    }
    Scsi_Command::Scsi_Command(void *f)
    { cam=(struct cam_device *)f, autoclose=0; fd=-1; filename=NULL;  }
    Scsi_Command::~Scsi_Command()
    { if (cam && autoclose)   cam_close_device(cam), cam=NULL;
      if (fd>=0)        close(fd);
      if (filename)           free(filename), filename=NULL;
    }

    int Scsi_Command::associate (const char *file,const struct stat *ref=NULL)
    { struct stat sb;
      char pass[32];          // periph_name is 16 chars long

      fd=open(file,O_RDONLY|O_NONBLOCK);

      // all if (ref) code is actually redundant, it never runs
      // as long as RELOAD_NEVER_NEEDED...
      if (ref && fd<0 && errno==EPERM)
      {   // expectedly we would get here if file is /dev/passN
          if (stat(file,&sb) < 0)         return 0;
          if (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev)
            return (errno=ENXIO,0);
          fd=open(file,O_RDWR);
      }

      if (fd < 0)                   return 0;
      if (fstat(fd,&sb) < 0)              return 0;
      if (!S_ISCHR(sb.st_mode))           return (errno=EINVAL,0);

      if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
          return (errno=ENXIO,0);

      memset (&ccb,0,sizeof(ccb));
      ccb.ccb_h.func_code = XPT_GDEVLIST;
      if (ioctl(fd,CAMGETPASSTHRU,&ccb)<0)      return (close(fd),fd=-1,0);

      sprintf (pass,"/dev/%.15s%u",ccb.cgdl.periph_name,ccb.cgdl.unit_number);
      cam=cam_open_pass (pass,O_RDWR,NULL);
      if (cam==NULL)                      return (close(fd),fd=-1,0);

      filename=strdup(file);

      return 1;
    }
    void Scsi_Command::timeout(int i)     { ccb.ccb_h.timeout=i*1000; }
    size_t Scsi_Command::residue()  { return ccb.csio.resid; }
    int Scsi_Command::transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
    { int ret=0;

      ccb.csio.ccb_h.flags |= dir;
      ccb.csio.data_ptr  = (u_int8_t *)buf;
      ccb.csio.dxfer_len = sz;

      if ((ret = cam_send_ccb(cam, &ccb)) < 0)
          return -1;

      if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
          return 0;

      unsigned char  *sense=(unsigned char *)&ccb.csio.sense_data;

      errno = EIO;
      // FreeBSD 5-CURRENT since 2003-08-24, including 5.2 fails to
      // pull sense data automatically, at least for ATAPI transport,
      // so I reach for it myself...
      if ((ccb.csio.scsi_status==SCSI_STATUS_CHECK_COND) &&
          !(ccb.ccb_h.status&CAM_AUTOSNS_VALID))
      {   u_int8_t  _sense[18];
          u_int32_t resid=ccb.csio.resid;

          memset(_sense,0,sizeof(_sense));

          operator[](0)      = 0x03;      // REQUEST SENSE
          ccb.csio.cdb_io.cdb_bytes[4] = sizeof(_sense);
          ccb.csio.cdb_len   = 6;
          ccb.csio.ccb_h.flags |= CAM_DIR_IN|CAM_DIS_AUTOSENSE;
          ccb.csio.data_ptr  = _sense;
          ccb.csio.dxfer_len = sizeof(_sense);
          ccb.csio.sense_len = 0;
          ret = cam_send_ccb(cam, &ccb);

          ccb.csio.resid = resid;
          if (ret<0)    return -1;
          if ((ccb.ccb_h.status&CAM_STATUS_MASK) != CAM_REQ_CMP)
            return errno=EIO,-1;

          memcpy(sense,_sense,sizeof(_sense));
      }

      ret = ERRCODE(sense);
      if (ret == 0)     ret = -1;
      else        CREAM_ON_ERRNO(sense);

      return ret;
    }
    int Scsi_Command::umount(int f=-1)
    { struct stat    fsb,msb;
      struct statfs *mntbuf;
      int            ret=0,mntsize,i;

      if (f==-1) f=fd;

      if (fstat (f,&fsb) < 0)                   return -1;
      if ((mntsize=getmntinfo(&mntbuf,MNT_NOWAIT))==0)return -1;

      for (i=0;i<mntsize;i++)
      {   if (stat (mntbuf[i].f_mntfromname,&msb) < 0) continue;
          if (msb.st_rdev == fsb.st_rdev)
          { ret=unmount (mntbuf[i].f_mntonname,0);
            break;
          }
      }

      return ret;
    }

int Scsi_Command::is_reload_needed ()
      {  return 0;   }

//*
#elif defined(_WIN32)

Scsi_Command::Scsi_Command()        { fd=INVALID_HANDLE_VALUE; autoclose=1; filename=NULL; }
Scsi_Command::Scsi_Command(void*f)  { fd=f, autoclose=0; filename=NULL; }
Scsi_Command::~Scsi_Command()
{
      DWORD junk;
      if (fd!=INVALID_HANDLE_VALUE && autoclose)
      {
            if (autoclose>1)
            DeviceIoControl(fd,FSCTL_UNLOCK_VOLUME, 
                  NULL,0,NULL,0,&junk,NULL);
            CloseHandle (fd),fd=INVALID_HANDLE_VALUE;
      }
      if (filename) free(filename),filename=NULL;
}
int   Scsi_Command::associate (const char *file,const struct stat *ref=NULL)
{
      char dev[32];
      sprintf(dev,"%.*s\\",sizeof(dev)-2,file);
      if (GetDriveType(dev)!=DRIVE_CDROM)
            return errno=EINVAL,0;
      sprintf(dev,"\\\\.\\%.*s",sizeof(dev)-5,file);
      fd=CreateFile (dev,GENERIC_WRITE|GENERIC_READ,
                     FILE_SHARE_READ|FILE_SHARE_WRITE,
                     NULL,OPEN_EXISTING,0,NULL);
      if (fd!=INVALID_HANDLE_VALUE)
            filename=strdup(dev);
      return fd!=INVALID_HANDLE_VALUE;
}
unsigned char Scsi_Command::*sense()      { return p.sense;    }
void  Scsi_Command::timeout(int i)  { p.spt.TimeOutValue=i; }
size_t      Scsi_Command::residue()        return 0; } // bogus
int   Scsi_Command::transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{
      DWORD bytes;
      int   ret=0;

      p.spt.DataBuffer = buf;
      p.spt.DataTransferLength = sz;
      p.spt.DataIn = dir;

      if (DeviceIoControl (fd,IOCTL_SCSI_PASS_THROUGH_DIRECT,
                        &p,sizeof(p.spt),
                        &p,sizeof(p),
                        &bytes,FALSE) == 0) return -1;

      if (p.sense[0]&0x70)
      {
            SetLastError (ERROR_GEN_FAILURE);
            ret = ERRCODE(p.sense);
            if (ret==0) ret=-1;
            else CREAM_ON_ERRNO(p.sense);
      }
#if 0
      else if (p.spt.Cdb[0] == 0x00)      // TEST UNIT READY
      { unsigned char _sense[18];

          operator[](0)   = 0x03;   // REQUEST SENSE
          p.spt.Cdb[4]    = sizeof(_sense);
          p.spt.CdbLength = 6;

          p.spt.DataBuffer = _sense;
          p.spt.DataTransferLength = sizeof(_sense);
          p.spt.DataIn = READ;

          if (DeviceIoControl (fd,IOCTL_SCSI_PASS_THROUGH_DIRECT,
                        &p,sizeof(p.spt),
                        &p,sizeof(p),
                        &bytes,FALSE) == 0) return -1;

          if ((ret = ERRCODE(_sense))) CREAM_ON_ERRNO(_sense);
      }
#endif
      return ret;
}
int   Scsi_Command::umount (int f=-1)
{
      DWORD junk;
      HANDLE h = (f==-1) ? fd : (HANDLE)f;

      if (DeviceIoControl(h,FSCTL_LOCK_VOLUME,NULL,0,NULL,0,&junk,NULL) &&
            DeviceIoControl(h,FSCTL_DISMOUNT_VOLUME,NULL,0,NULL,0,&junk,NULL))
      {
            if (h==fd) autoclose++;
            return 0;
      }
      return -1;
}

int   Scsi_Command::is_reload_needed ()   {   return 0;   }

//*/

#else
#error "Unsupported OS"
#endif

#undef ERRCODE
#undef CREAM_ON_ERRNO
#undef CREAM_ON_ERRNO_NAKED


int sense2str(int err, char* str) {
      strcpy(str,"[unknown error]");
      switch (SK(err)) {
      case 0x1:
            switch (ASC(err)) {
            case 0x0B:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"WARNING"); break;
                  case 0x01: strcpy(str,"WARNING - SPECIFIED TEMPERATURE EXCEEDED"); break;
                  case 0x02: strcpy(str,"WARNING - ENCLOSURE DEGRADED"); break;

                  default:  sprintf(str,"WARNING, ASCQ=%02X",ASCQ(err)); break;
                  }
                  break;
            case 0x17:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"RECOVERED DATA WITH NO ERROR CORRECTION APPLIED"); break;
                  case 0x01: strcpy(str,"RECOVERED DATA WITH RETRIES"); break;
                  case 0x02: strcpy(str,"RECOVERED DATA WITH POSITIVE HEAD OFFSET"); break;
                  case 0x03: strcpy(str,"RECOVERED DATA WITH NEGATIVE HEAD OFFSET"); break;
                  case 0x04: strcpy(str,"RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED"); break;
                  case 0x05: strcpy(str,"RECOVERED DATA USING PREVIOUS SECTOR ID"); break;
                  case 0x07: strcpy(str,"RECOVERED DATA WITHOUT ECC - RECOMMEND REASSIGNMENT"); break;
                  case 0x08: strcpy(str,"RECOVERED DATA WITHOUT ECC - RECOMMEND REWRITE"); break;
                  case 0x09: strcpy(str,"RECOVERED DATA WITHOUT ECC - DATA REWRITTEN"); break;

                  default:   strcpy(str,"RECOVERED DATA WITH NO ERROR CORRECTION APPLIED"); break;
                  }
                  break;
            case 0x18:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"RECOVERED DATA WITH ERROR CORRECTION APPLIED"); break;
                  case 0x01: strcpy(str,"RECOVERED DATA WITH ERROR CORR. & RETRIES APPLIED"); break;
                  case 0x02: strcpy(str,"RECOVERED DATA - DATA AUTO-REALLOCATED"); break;
                  case 0x03: strcpy(str,"RECOVERED DATA WITH CIRC"); break;
                  case 0x04: strcpy(str,"RECOVERED DATA WITH L-EC"); break;
                  case 0x05: strcpy(str,"RECOVERED DATA - RECOMMEND REASSIGNMENT"); break;
                  case 0x06: strcpy(str,"RECOVERED DATA - RECOMMEND REWRITE"); break;
                  case 0x08: strcpy(str,"RECOVERED DATA WITH LINKING"); break;

                  default:   strcpy(str,"RECOVERED DATA WITH ERROR CORRECTION APPLIED"); break;
                  }
                  break;
            case 0x5D:
                  switch (ASCQ(err)) {
                  case 0x01: strcpy(str,"FAILURE PREDICTION THRESHOLD EXCEEDED - Predicted Media failure"); break;
                  case 0x02: strcpy(str,"LOGICAL UNIT FAILURE PREDICTION THRESHOLD EXCEEDED"); break;
                  case 0x03: strcpy(str,"FAILURE PREDICTION THRESHOLD EXCEEDED - Predicted Spare Area Exhaustion"); break;
                  case 0xFF: strcpy(str,"FAILURE PREDICTION THRESHOLD EXCEEDED (FALSE)"); break;

                  default:   strcpy(str,"LOGICAL UNIT FAILURE PREDICTION THRESHOLD EXCEEDED"); break;
                  }
                  break;
            case 0x73:
                  switch (ASCQ(err)) {
                  case 0x01: strcpy(str,"POWER CALIBRATION AREA ALMOST FULL"); break;
                  case 0x06: strcpy(str,"RMA/PMA IS ALMOST FULL"); break;
                  }
                  break;
            }
      case 0x2:
            switch (ASC(err)) {
            case 0x04:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE"); break;
                  case 0x01: strcpy(str,"LOGICAL UNIT IS IN PROCESS OF BECOMING READY"); break;
                  case 0x02: strcpy(str,"LOGICAL UNIT NOT READY, INITIALIZING CMD. REQUIRED"); break;
                  case 0x03: strcpy(str,"LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED"); break;
                  case 0x04: strcpy(str,"LOGICAL UNIT NOT READY, FORMAT IN PROGRESS"); break;
                  case 0x07: strcpy(str,"LOGICAL UNIT NOT READY, OPERATION IN PROGRESS"); break;
                  case 0x08: strcpy(str,"LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS"); break;

                  default:   strcpy(str,"LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE"); break;
                  }
                  break;
            case 0x30:
                  switch (ASCQ(err)) {
                        case 0x00: strcpy(str,"INCOMPATIBLE MEDIUM INSTALLED"); break;
                        case 0x01: strcpy(str,"CANNOT READ MEDIUM - UNKNOWN FORMAT"); break;
                        case 0x02: strcpy(str,"CANNOT READ MEDIUM - INCOMPATIBLE FORMAT"); break;
                        case 0x03: strcpy(str,"CLEANING CARTRIDGE INSTALLED"); break;
                        case 0x04: strcpy(str,"CANNOT WRITE MEDIUM - UNKNOWN FORMAT"); break;
                        case 0x05: strcpy(str,"CANNOT WRITE MEDIUM - INCOMPATIBLE FORMAT"); break;
                        case 0x06: strcpy(str,"CANNOT FORMAT MEDIUM - INCOMPATIBLE MEDIUM"); break;
                        case 0x07: strcpy(str,"CLEANING FAILURE"); break;
                        case 0x11: strcpy(str,"CANNOT WRITE MEDIUM - UNSUPPORTED MEDIUM VERSION"); break;

                        default:   strcpy(str,"INCOMPATIBLE MEDIUM INSTALLED"); break;
                  }
                  break;
            case 0x3A:
                  switch (ASCQ(err)) {
                        case 0x00: strcpy(str,"MEDIUM NOT PRESENT"); break;
                        case 0x01: strcpy(str,"MEDIUM NOT PRESENT - TRAY CLOSED"); break;
                        case 0x02: strcpy(str,"MEDIUM NOT PRESENT - TRAY OPEN"); break;

                        default:   strcpy(str,"MEDIUM NOT PRESENT"); break;
                  }
                  break;
            case 0x3E: strcpy(str,"LOGICAL UNIT HAS NOT SELF-CONFIGURED YET"); break; /* ASCQ=00: */
            }
            break;
      case 0x3:
            switch (ASC(err)) {
            case 0x02: strcpy(str,"NO SEEK COMPLETE"); break; /* ASCQ = 0x00 */
            case 0x06: strcpy(str,"NO REFERENCE POSITION FOUND"); break;
            case 0x0C:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"WRITE ERROR"); break;
                  case 0x07: strcpy(str,"WRITE ERROR - RECOVERY NEEDED"); break;
                  case 0x08: strcpy(str,"WRITE ERROR - RECOVERY FAILED"); break;
                  case 0x09: strcpy(str,"WRITE ERROR - LOSS OF STREAMING"); break;
                  case 0x0A: strcpy(str,"WRITE ERROR - PADDING BLOCKS ADDED"); break;

                  default:   strcpy(str,"WRITE ERROR"); break;
                  }
                  break;
            case 0x11:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"UNRECOVERED READ ERROR"); break;
                  case 0x01: strcpy(str,"READ RETRIES EXHAUSTED"); break;
                  case 0x02: strcpy(str,"ERROR TOO LONG TO CORRECT"); break;
                  case 0x05: strcpy(str,"L-EC UNCORRECTABLE ERROR"); break;
                  case 0x06: strcpy(str,"CIRC UNRECOVERED ERROR"); break;
                  case 0x0F: strcpy(str,"ERROR READING UPC/EAN NUMBER"); break;
                  case 0x10: strcpy(str,"ERROR READING ISRC NUMBER"); break;

                  default:   strcpy(str,"UNRECOVERED READ ERROR"); break;
                  }
                  break;
            case 0x15:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"RANDOM POSITIONING ERROR"); break;
                  case 0x01: strcpy(str,"MECHANICAL POSITIONING ERROR"); break;
                  case 0x02: strcpy(str,"POSITIONING ERROR DETECTED BY READ OF MEDIUM"); break;

                  default: strcpy(str,"RANDOM POSITIONING ERROR"); break;
                  }
                  break;
            case 0x31:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"MEDIUM FORMAT CORRUPTED"); break;
                  case 0x01: strcpy(str,"FORMAT COMMAND FAILED"); break;
                  case 0x02: strcpy(str,"ZONED FORMATTING FAILED DUE TO SPARE LINKING"); break;

                  default:   strcpy(str,"MEDIUM FORMAT CORRUPTED"); break;
                  }
                  break;
            case 0x51:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"ERASE FAILURE"); break;
                  case 0x01: strcpy(str,"ERASE FAILURE - INCOMPLETE ERASE OPERATION DETECTED"); break;

                  default:   strcpy(str,"ERASE FAILURE"); break;
                  }
                  break;
            case 0x57: strcpy(str,"UNABLE TO RECOVER TABLE-OF-CONTENTS"); break; /* ASCQ = 00 */
            case 0x72:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"SESSION FIXATION ERROR"); break;
                  case 0x01: strcpy(str,"SESSION FIXATION ERROR WRITING LEAD-IN"); break;
                  case 0x02: strcpy(str,"SESSION FIXATION ERROR WRITING LEAD-OUT"); break;

                  default:   strcpy(str,"SESSION FIXATION ERROR"); break;
                  }
                  break;
            case 0x73:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"CD CONTROL ERROR"); break;
                  case 0x02: strcpy(str,"POWER CALIBRATION AREA IS FULL"); break;
                  case 0x03: strcpy(str,"POWER CALIBRATION AREA ERROR"); break;
                  case 0x04: strcpy(str,"PROGRAM MEMORY AREA UPDATE FAILURE"); break;
                  case 0x05: strcpy(str,"PROGRAM MEMORY AREA IS FULL"); break;

                  default:   strcpy(str,"CD CONTROL ERROR"); break;
                  }
                  break;
            }
            break;
      case 0x4:
            switch (ASC(err)) {
            case 0x00: strcpy(str,"CLEANING REQUESTED"); break;  /* ASCQ = 0x17 */
            case 0x05: strcpy(str,"LOGICAL UNIT DOES NOT RESPOND TO SELECTION"); break; /* ASCQ = 0x00 */
            case 0x08:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"LOGICAL UNIT COMMUNICATION FAILURE"); break;
                  case 0x01: strcpy(str,"LOGICAL UNIT COMMUNICATION TIMEOUT"); break;
                  case 0x02: strcpy(str,"LOGICAL UNIT COMMUNICATION PARITY ERROR"); break;
                  case 0x03: strcpy(str,"LOGICAL UNIT COMMUNICATION CRC ERROR (ULTRA-DMA/32)"); break;
                  }
                  break;
            case 0x09:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"TRACK FOLLOWING ERROR"); break;
                  case 0x01: strcpy(str,"TRACKING SERVO FAILURE"); break;
                  case 0x02: strcpy(str,"FOCUS SERVO FAILURE"); break;
                  case 0x03: strcpy(str,"SPINDLE SERVO FAILURE"); break;
                  case 0x04: strcpy(str,"HEAD SELECT FAULT"); break;

                  default:   strcpy(str,"TRACKING ERROR"); break;
                  }
                  break;
            case 0x15:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"RANDOM POSITIONING ERROR"); break;
                  case 0x01: strcpy(str,"MECHANICAL POSITIONING ERROR"); break;

                  default:   strcpy(str,"RANDOM POSITIONING ERROR"); break;
                  }
                  break;
            case 0x1B: strcpy(str,"SYNCHRONOUS DATA TRANSFER ERROR"); break; /* ASCQ = 0x00 */
            case 0x3B: strcpy(str,"MECHANICAL POSITIONING OR CHANGER ERROR"); break; /* ASCQ = 0x16 */
            case 0x3E:
                  switch (ASCQ(err)) {
                  case 0x01: strcpy(str,"LOGICAL UNIT FAILURE"); break;
                  case 0x02: strcpy(str,"TIMEOUT ON LOGICAL UNIT"); break;

                  default:   strcpy(str,"LOGICAL UNIT FAILURE"); break;
                  }
                  break;
            case 0x40: strcpy(str,"DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH)"); break;
            case 0x44: strcpy(str,"INTERNAL TARGET FAILURE"); break;
            case 0x46: strcpy(str,"UNSUCCESSFUL SOFT RESET"); break;
            case 0x47: strcpy(str,"SCSI PARITY ERROR"); break;
            case 0x4A: strcpy(str,"COMMAND PHASE ERROR"); break;
            case 0x4B: strcpy(str,"DATA PHASE ERROR"); break;
            case 0x4C: strcpy(str,"LOGICAL UNIT FAILED SELF-CONFIGURATION"); break;
            case 0x53: strcpy(str,"MEDIA LOAD OR EJECT FAILED"); break;
            case 0x65: strcpy(str,"VOLTAGE FAULT"); break;
            }
            break;
      case 0x5:
            switch (ASC(err)) {
            case 0x07: strcpy(str,"MULTIPLE PERIPHERAL DEVICES SELECTED"); break; /* ASCQ = 0x00 */
            case 0x1A: strcpy(str,"PARAMETER LIST LENGTH ERROR"); break; /* ASCQ = 0x00 */
            case 0x20: strcpy(str,"INVALID COMMAND OPERATION CODE"); break; /* ASCQ = 0x00 */
            case 0x21:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"LOGICAL BLOCK ADDRESS OUT OF RANGE"); break;
                  case 0x01: strcpy(str,"INVALID ELEMENT ADDRESS"); break;
                  case 0x02: strcpy(str,"INVALID ADDRESS FOR WRITE"); break;

                  default:   strcpy(str,"LOGICAL BLOCK ADDRESS OUT OF RANGE"); break;
                  }
                  break;
            case 0x24: strcpy(str,"INVALID FIELD IN CDB"); break;
            case 0x25: strcpy(str,"LOGICAL UNIT NOT SUPPORTED"); break;
            case 0x26:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"INVALID FIELD IN PARAMETER LIST"); break;
                  case 0x01: strcpy(str,"PARAMETER NOT SUPPORTED"); break;
                  case 0x02: strcpy(str,"PARAMETER VALUE INVALID"); break;
                  case 0x03: strcpy(str,"THRESHOLD PARAMETERS NOT SUPPORTED"); break;
                  }
                  break;
            case 0x2B: strcpy(str,"COPY CANNOT EXECUTE SINCE INITIATOR CANNOT DISCONNECT"); break; /* ASCQ = 0x00 */
            case 0x2C:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"COMMAND SEQUENCE ERROR"); break;
                  case 0x03: strcpy(str,"CURRENT PROGRAM AREA IS NOT EMPTY"); break;
                  case 0x04: strcpy(str,"CURRENT PROGRAM AREA IS EMPTY"); break;
                  }
                  break;
            case 0x30:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"INCOMPATIBLE MEDIUM INSTALLED"); break;
                  case 0x01: strcpy(str,"CANNOT READ MEDIUM - UNKNOWN FORMAT"); break;
                  case 0x02: strcpy(str,"CANNOT READ MEDIUM - INCOMPATIBLE FORMAT"); break;
                  case 0x03: strcpy(str,"CLEANING CARTRIDGE INSTALLED"); break;
                  case 0x04: strcpy(str,"CANNOT WRITE MEDIUM - UNKNOWN FORMAT"); break;
                  case 0x05: strcpy(str,"CANNOT WRITE MEDIUM - INCOMPATIBLE FORMAT"); break;
                  case 0x06: strcpy(str,"CANNOT FORMAT MEDIUM - INCOMPATIBLE MEDIUM"); break;
                  case 0x07: strcpy(str,"CLEANING FAILURE"); break;
                  case 0x08: strcpy(str,"CANNOT WRITE - APPLICATION CODE MISMATCH"); break;
                  case 0x09: strcpy(str,"CURRENT SESSION NOT FIXATED FOR APPEND"); break;
                  case 0x10: strcpy(str,"MEDIUM NOT FORMATTED"); break;
                  }
                  break;
            case 0x39: strcpy(str,"SAVING PARAMETERS NOT SUPPORTED"); break;  /* ASCQ = 0x00 */
            case 0x3D: strcpy(str,"INVALID BITS IN IDENTIFY MESSAGE"); break; /* ASCQ = 0x00 */
            case 0x43: strcpy(str,"MESSAGE ERROR"); break; /* ASCQ = 0x00 */
            case 0x53: strcpy(str,"MEDIUM REMOVAL PREVENTED"); break; /* ASCQ = 0x02 */
            case 0x64:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"ILLEGAL MODE FOR THIS TRACK"); break;
                  case 0x01: strcpy(str,"INVALID PACKET SIZE"); break;
                  }
                  break;
            case 0x6F:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"COPY PROTECTION KEY EXCHANGE FAILURE - AUTHENTICATION FAILURE"); break;
                  case 0x01: strcpy(str,"COPY PROTECTION KEY EXCHANGE FAILURE - KEY NOT PRESENT"); break;
                  case 0x02: strcpy(str,"COPY PROTECTION KEY EXCHANGE FAILURE - KEY NOT ESTABLISHED"); break;
                  case 0x03: strcpy(str,"READ OF SCRAMBLED SECTOR WITHOUT AUTHENTICATION"); break;
                  case 0x04: strcpy(str,"MEDIA REGION CODE IS MISMATCHED TO LOGICAL UNIT REGION"); break;
                  case 0x05: strcpy(str,"LOGICAL UNIT REGION MUST BE PERMANENT/REGION RESET COUNT ERROR"); break;
                  }
                  break;
            case 0x72:
                  switch (ASCQ(err)) {
                  case 0x03: strcpy(str,"SESSION FIXATION ERROR . INCOMPLETE TRACK IN SESSION"); break;
                  case 0x04: strcpy(str,"EMPTY OR PARTIALLY WRITTEN RESERVED TRACK"); break;
                  case 0x05: strcpy(str,"NO MORE TRACK RESERVATIONS ALLOWED"); break;
                  }
                  break;
            }
            break;
      case 0x6:
            switch (ASC(err)) {
            case 0x28:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED"); break;
                  case 0x01: strcpy(str,"IMPORT OR EXPORT ELEMENT ACCESSED"); break;
                  }
                  break;
            case 0x29:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"POWER ON, RESET, OR BUS DEVICE RESET OCCURRED"); break;
                  case 0x01: strcpy(str,"POWER ON OCCURRED"); break;
                  case 0x02: strcpy(str,"BUS RESET OCCURRED"); break;
                  case 0x03: strcpy(str,"BUS DEVICE RESET FUNCTION OCCURRED"); break;
                  case 0x04: strcpy(str,"DEVICE INTERNAL RESET"); break;
                  }
                  break;
            case 0x2A:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"PARAMETERS CHANGED"); break;
                  case 0x01: strcpy(str,"MODE PARAMETERS CHANGED"); break;
                  case 0x02: strcpy(str,"LOG PARAMETERS CHANGED"); break;
                  case 0x03: strcpy(str,"RESERVATIONS PREEMPTED"); break;
                  }
                  break;
            case 0x2E: strcpy(str,"INSUFFICIENT TIME FOR OPERATION"); break;
            case 0x2F: strcpy(str,"COMMANDS CLEARED BY ANOTHER INITIATOR"); break;
            case 0x3B:
                  switch (ASCQ(err)) {
                  case 0x0D: strcpy(str,"MEDIUM DESTINATION ELEMENT FULL"); break;
                  case 0x0E: strcpy(str,"MEDIUM SOURCE ELEMENT EMPTY"); break;
                  case 0x0F: strcpy(str,"END OF MEDIUM REACHED"); break;
                  case 0x11: strcpy(str,"MEDIUM MAGAZINE NOT ACCESSIBLE"); break;
                  case 0x12: strcpy(str,"MEDIUM MAGAZINE REMOVED"); break;
                  case 0x13: strcpy(str,"MEDIUM MAGAZINE INSERTED"); break;
                  case 0x14: strcpy(str,"MEDIUM MAGAZINE LOCKED"); break;
                  case 0x15: strcpy(str,"MEDIUM MAGAZINE UNLOCKED"); break;
                  }
                  break;
            case 0x3F:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"TARGET OPERATING CONDITIONS HAVE CHANGED"); break;
                  case 0x01: strcpy(str,"MICROCODE HAS BEEN CHANGED"); break;
                  case 0x02: strcpy(str,"CHANGED OPERATING DEFINITION"); break;
                  case 0x03: strcpy(str,"INQUIRY DATA HAS CHANGED"); break;
                  }
                  break;
            case 0x5A:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"OPERATOR REQUEST OR STATE CHANGE INPUT"); break;
                  case 0x01: strcpy(str,"OPERATOR MEDIUM REMOVAL REQUEST"); break;
                  case 0x02: strcpy(str,"OPERATOR SELECTED WRITE PROTECT"); break;
                  case 0x03: strcpy(str,"OPERATOR SELECTED WRITE PERMIT"); break;
                  }
                  break;
            case 0x5B:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"LOG EXCEPTION"); break;
                  case 0x01: strcpy(str,"THRESHOLD CONDITION MET"); break;
                  case 0x02: strcpy(str,"LOG COUNTER AT MAXIMUM"); break;
                  case 0x03: strcpy(str,"LOG LIST CODES EXHAUSTED"); break;
                  }
                  break;
            case 0x5E:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"LOW POWER CONDITION ON"); break;
                  case 0x01: strcpy(str,"IDLE CONDITION ACTIVATED BY TIMER"); break;
                  case 0x02: strcpy(str,"STANDBY CONDITION ACTIVATED BY TIMER"); break;
                  case 0x03: strcpy(str,"IDLE CONDITION ACTIVATED BY COMMAND"); break;
                  case 0x04: strcpy(str,"STANDBY CONDITION ACTIVATED BY COMMAND"); break;
                  }
                  break;
            }
            break;
      case 0x7:
            switch (ASC(err)) {
            case 0x27:
                  switch (ASCQ(err)) {
                  case 0x00: strcpy(str,"WRITE PROTECTED"); break;
                  case 0x01: strcpy(str,"HARDWARE WRITE PROTECTED"); break;
                  case 0x02: strcpy(str,"LOGICAL UNIT SOFTWARE WRITE PROTECTED"); break;
                  case 0x03: strcpy(str,"ASSOCIATED WRITE PROTECT"); break;
                  case 0x04: strcpy(str,"PERSISTENT WRITE PROTECT"); break;
                  case 0x05: strcpy(str,"PERMANENT WRITE PROTECT"); break;
                  case 0x06: strcpy(str,"CONDITIONAL WRITE PROTECT"); break;

                  default:   strcpy(str,"WRITE PROTECTED"); break;
                  }
                  break;
            }
            break;
      case 0x8: strcpy(str,"BLANK CHECK"); break;
      case 0xB:
            switch (ASC(err)) {
            case 0x00: strcpy(str,"I/O PROCESS TERMINATED"); break; /* ASCQ = 06 */
            case 0x11: strcpy(str,"READ ERROR - LOSS OF STREAMING"); break; /* ASCQ = 11 */
            case 0x45: strcpy(str,"SELECT OR RESELECT FAILURE"); break; /* ASCQ = 00 */
            case 0x48: strcpy(str,"INITIATOR DETECTED ERROR MESSAGE RECEIVED"); break; /* ASCQ = 00 */
            case 0x49: strcpy(str,"INVALID MESSAGE ERROR"); break; /* ASCQ = 00 */
            case 0x4D: strcpy(str,"TAGGED OVERLAPPED COMMANDS (NN = QUEUE TAG)"); break; /* ASCQ = xx */
            }
            break;
      }
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index