Blob Blame History Raw
/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2002 Andreas Mueller <andreas@daneb.de>
 *
 *  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.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

#include <sys/types.h>

#include <sys/wait.h>
#include <sys/utsname.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <iostream>
#include <fstream>
#include <stdarg.h>
#include <signal.h>
#include <ctype.h>
#include <list>
#include <string>
#include <assert.h>

#include "log.h"
#include "util.h"
#include "Toc.h"
#include "ScsiIf.h"
#include "CdrDriver.h"
#include "dao.h"
#include "port.h"
#include "Settings.h"
#include "Cddb.h"
#include "TempFileManager.h"
#include "FormatConverter.h"

#ifdef __CYGWIN__
#define NOMINMAX
#include <windows.h>
#include <winioctl.h>
#define IOCTL_SCSI_BASE                 FILE_DEVICE_CONTROLLER
#define IOCTL_SCSI_GET_CAPABILITIES     CTL_CODE(IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SCSI_PASS_THROUGH_DIRECT  CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_SCSI_GET_ADDRESS          CTL_CODE(IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS)
#endif

#ifdef UNIXWARE
extern "C" {
  extern int seteuid(uid_t);
  extern int setegid(uid_t);
};
#endif

typedef enum {
    UNKNOWN = -1,
    SHOW_TOC,
    SHOW_DATA,
    READ_TEST,
    SIMULATE,
    WRITE,
    READ_TOC,
    DISK_INFO,
    READ_CD,
    TOC_INFO,
    TOC_SIZE,
    BLANK,
    SCAN_BUS,
    UNLOCK,
    COPY_CD,
    READ_CDDB,
    MSINFO,
    DRIVE_INFO,
    DISCID,
    SHOW_VERSION,
    LAST_CMD,
} DaoCommand;

typedef enum {
    NO_DEVICE,
    NEED_CDR_R,
    NEED_CDR_W,
    NEED_CDRW_W,
} DaoDeviceType;

// The cmdInfo[] array provides information about each of cdrdao's
// main commands, including some of the basic processing steps
// required, almost as a simplified state machine.

static struct {
    // The command code, which is also the index into the array
    DaoCommand cmd;
    // The command-line string for this command
    const char* str;
    // What type of device does the command require, for device
    // auto-detection.
    DaoDeviceType requiredDevice;
    // Does the command require a toc file
    bool needTocFile;
    // Does the command require to parse an existing toc file
    bool tocParse;
    // Does the command require to check the parsed toc file
    bool tocCheck;
} cmdInfo[LAST_CMD] = {
    { SHOW_TOC,     "show-toc",   NO_DEVICE,   1, 1, 0 },
    { SHOW_DATA,    "show-data",  NO_DEVICE,   1, 1, 1 },
    { READ_TEST,    "read-test",  NO_DEVICE,   1, 1, 1 },
    { SIMULATE,     "simulate",   NEED_CDR_W,  1, 1, 1 },
    { WRITE,        "write",      NEED_CDR_W,  1, 1, 1 },
    { READ_TOC,     "read-toc",   NEED_CDR_R,  1, 0, 1 },
    { DISK_INFO,    "disk-info",  NEED_CDR_R,  0, 0, 1 },
    { READ_CD,      "read-cd",    NEED_CDR_R,  1, 0, 1 },
    { TOC_INFO,     "toc-info",   NO_DEVICE,   1, 1, 1 },
    { TOC_SIZE,     "toc-size",   NO_DEVICE,   1, 1, 1 },
    { BLANK,        "blank",      NEED_CDRW_W, 0, 0, 1 },
    { SCAN_BUS,     "scanbus",    NO_DEVICE,   0, 0, 1 },
    { UNLOCK,       "unlock",     NEED_CDR_R,  0, 0, 1 },
    { COPY_CD,      "copy",       NEED_CDR_W,  0, 0, 1 },
    { READ_CDDB,    "read-cddb",  NO_DEVICE,   1, 1, 0 },
    { MSINFO,       "msinfo",     NEED_CDR_R,  0, 0, 1 },
    { DRIVE_INFO,   "drive-info", NEED_CDR_R,  0, 0, 1 },
    { DISCID,       "discid",     NEED_CDR_R,  0, 0, 1 },
    { SHOW_VERSION, "version",    NO_DEVICE,   0, 0, 0 },
};

typedef struct {

    DaoCommand command;

    const char* progName;
    const char* tocFile;
    const char* driverId;
    const char* sourceDriverId;
    const char* scsiDevice;
    const char* sourceScsiDevice;
    const char* dataFilename;
    const char* cddbLocalDbDir;
    const char* tmpFileDir;
    const char* cddbServerList;

    int  readingSpeed;
    int  writingSpeed;
    bool eject;
    bool swap;
    bool multiSession;
    int  verbose;
    int  session;
    int  fifoBuffers;
    bool fastToc;
    bool pause;
    bool readRaw;
    bool mode2Mixed;
    bool remoteMode;
    int  remoteFd;
    bool reload;
    bool force;
    int  paranoiaMode;
    bool onTheFly;
    bool writeSimulate;
    bool saveSettings;
    int  userCapacity;
    bool fullBurn;
    int  cddbTimeout;
    bool withCddb;
    bool taoSource;
    int  taoSourceAdjust;
    bool keepImage;
    bool overburn;
    int  bufferUnderrunProtection;
    bool writeSpeedControl;
    bool keep;
    bool printQuery;

    CdrDriver::BlankingMode blankingMode;
    TrackData::SubChannelMode readSubchanMode;

} DaoCommandLine;

static bool isNT = false;

#ifdef __CYGWIN__
/*! \brief OS handle to the device
	As obtained from CreateFile, used to apply OS level locking.
*/
static HANDLE fh = NULL;
/*! \brief Device string
	Like "\\\\.\\E:", used in CreateFile to obtain handle to device.
*/
static char devstr[10];
#endif

static void printVersion()
{
  log_message(2, "Cdrdao version %s - (C) Andreas Mueller <andreas@daneb.de>",
	  VERSION);

  std::list<std::string> list;
  int num = formatConverter.supportedExtensions(list);

  if (num) {
    std::string msg = "Format converter enabled for extensions:";
    std::list<std::string>::iterator i = list.begin();
    for (;i != list.end(); i++) {
      msg += " ";
      msg += (*i);
    }
    log_message(3, msg.c_str());
  }
}

static void setOptionDefaults(DaoCommandLine* options)
{
    memset(options, 0, sizeof(DaoCommandLine));

    options->readingSpeed = -1;
    options->writingSpeed = -1;
    options->command = UNKNOWN;
    options->verbose = 2;
    options->session = 1;
    options->pause = true;
    options->mode2Mixed = true;
    options->remoteFd = -1;
    options->paranoiaMode = 3;
    options->cddbTimeout = 60;
    options->taoSourceAdjust = -1;
    options->bufferUnderrunProtection = 1;
    options->writeSpeedControl = true;
    options->keep = false;
    options->printQuery = false;
#if defined(__FreeBSD__)
    options->fifoBuffers = 20;
#else
    options->fifoBuffers = 32;
#endif
    options->cddbServerList = "freedb.freedb.org freedb.freedb.org"
	":/~cddb/cddb.cgi uk.freedb.org uk.freedb.org:/~cddb/cddb.cgi"
	"cz.freedb.org cz.freedb.org:/~cddb/cddb.cgi";

    options->blankingMode = CdrDriver::BLANK_MINIMAL;
    options->readSubchanMode = TrackData::SUBCHAN_NONE;
}

static void printUsage(DaoCommandLine* options)
{
    switch (options->command) {

  case UNKNOWN:
    log_message(0, "\nUsage: %s <command> [options] [toc-file]",
		options->progName);
    log_message(0,
"command:\n"
"  show-toc   - prints out toc and exits\n"
"  toc-info   - prints out short toc-file summary\n"
"  toc-size   - prints total number of blocks for toc\n"
"  read-toc   - create toc file from audio CD\n"
"  read-cd    - create toc and rip audio data from CD\n"
"  read-cddb  - contact CDDB server and add data as CD-TEXT to toc-file\n"
"  show-data  - prints out audio data and exits\n"
"  read-test  - reads all audio files and exits\n"
"  disk-info  - shows information about inserted medium\n"
"  discid     - prints out CDDB information\n"
"  msinfo     - shows multi session info, output is suited for scripts\n"
"  drive-info - shows drive information\n"
"  unlock     - unlock drive after failed writing\n"
"  blank      - blank a CD-RW\n"
"  scanbus    - scan for devices\n"
"  simulate   - shortcut for 'write --simulate'\n"
"  write      - writes CD\n"
"  copy       - copies CD\n");
    
    log_message(0, "\n Try '%s <command> -h' to get a list of available "
		"options\n", options->progName);
    break;
    
  case SHOW_TOC:
    log_message(0, "\nUsage: %s show-toc [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --tmpdir <path>         - sets directory for temporary wav files\n"
"  --keep                  - keep generated temp wav files after exit\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case SHOW_DATA:
    log_message(0, "\nUsage: %s show-data [--swap] [-v #] toc-file\n",
		options->progName);
    break;
    
  case READ_TEST:
    log_message(0, "\nUsage: %s read-test [-v #] toc-file\n",
		options->progName);
    break;
  
  case SIMULATE:
    log_message(0, "\nUsage: %s simulate [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --speed <writing-speed> - selects writing speed\n"
"  --multi                 - session will not be closed\n"
"  --overburn              - allow to overburn a medium\n"
"  --full-burn             - force burning to the outer disk edge\n"
"                            with '--driver generic-mmc-raw'\n"
"  --capacity <minutes>    - sets disk capacity for '--full-burn'\n"
"                            you must specify this when using blanks bigger\n"
"                            than 80 min. (90,99,etc.)\n"
"                            because they seems like 80 min. blanks\n"
"  --eject                 - ejects cd after simulation\n"
"  --swap                  - swap byte order of audio files\n"
"  --buffers #             - sets fifo buffer size (min. 10)\n"
"  --reload                - reload the disk if necessary for writing\n"
"  --force                 - force execution of operation\n"
"  --tmpdir <path>         - sets directory for temporary wav files\n"
"  --keep                  - keep generated temp wav files after exit\n"
"  -v #                    - sets verbose level\n"
"  -n                      - no pause before writing\n");
    break;
    
  case WRITE:
    log_message(0, "\nUsage: %s write [options] toc-file", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --simulate              - just perform a write simulation\n"
"  --speed <writing-speed> - selects writing speed\n"
"  --multi                 - session will not be closed\n"
"  --buffer-under-run-protection #\n"
"                          - 0: disable buffer under run protection\n"
"                            1: enable buffer under run protection (default)\n"
"  --write-speed-control # - 0: disable writing speed control by the drive\n"
"                            1: enable writing speed control (default)\n" 
"  --overburn              - allow to overburn a medium\n"
"  --full-burn             - force burning to the outer disk edge\n"
"                            with '--driver generic-mmc-raw'\n"
"  --capacity <minutes>    - sets disk capacity for '--full-burn'\n"
"                            you must specify this when using blanks bigger\n"
"                            than 80 min. (90,99,etc.)\n"
"                            because they seems like 80 min. blanks\n"
"  --eject                 - ejects cd after writing or simulation\n"
"  --swap                  - swap byte order of audio files\n"
"  --buffers #             - sets fifo buffer size (min. 10)\n"
"  --reload                - reload the disk if necessary for writing\n"
"  --force                 - force execution of operation\n"
"  --tmpdir <path>         - sets directory for temporary wav files\n"
"  --keep                  - keep generated temp wav files after exit\n"
"  -v #                    - sets verbose level\n"
"  -n                      - no pause before writing\n");
    break;
    
  case READ_TOC:
    log_message(0, "\nUsage: %s read-toc [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-ROM reader\n"
"  --driver <id>    - force usage of specified driver for source device\n"
"  --datafile <filename>   - name of data file placed in toc-file\n"
"  --session #             - select session\n"
"  --fast-toc              - do not extract pre-gaps and index marks\n"
"  --read-raw              - select raw sectors modes for data tracks\n"
"  --no-mode2-mixed        - don't switch to mode2_mixed\n"
"  --rspeed <read-speed>   - selects reading speed\n"
"  --read-subchan <mode>   - defines sub-channel reading mode\n"
"                            <mode> = rw | rw_raw\n"
"  --tao-source            - indicate that source CD was written in TAO mode\n"
"  --tao-source-adjust #   - # of link blocks for TAO source CDs (def. 2)\n"
"  --with-cddb             - retrieve CDDB CD-TEXT data while copying\n"
"  --cddb-servers <list>   - sets space separated list of CDDB servers\n"
"  --cddb-timeout #        - timeout in seconds for CDDB server communication\n"
"  --cddb-directory <path> - path to local CDDB directory where fetched\n"
"                            CDDB records will be stored\n"
"  --force                 - force execution of operation\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case DISK_INFO:
    log_message(0, "\nUsage: %s disk-info [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case DISCID:
    log_message(0, "\nUsage: %s discid [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --cddb-servers <list>   - sets space separated list of CDDB servers\n"
"  --cddb-timeout #        - timeout in seconds for CDDB server communication\n"
"  --cddb-directory <path> - path to local CDDB directory where fetched\n"
"                            CDDB records will be stored\n"
"  --query-string          - prints out CDDB query only\n"
"  -v #                    - sets verbose level\n");
    break;
   
  case READ_CD:
    log_message(0, "\nUsage: %s read-cd [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-ROM reader\n"
"  --driver <id>    - force usage of specified driver for source device\n"
"  --datafile <filename>   - name of data file placed in toc-file\n"
"  --session #             - select session\n"
"  --fast-toc              - do not extract pre-gaps and index marks\n"
"  --read-raw              - read raw data sectors (including L-EC data)\n"
"  --no-mode2-mixed        - don't switch to mode2_mixed\n"
"  --rspeed <read-speed>   - selects reading speed\n"
"  --read-subchan <mode>   - defines sub-channel reading mode\n"
"                            <mode> = rw | rw_raw\n"
"  --tao-source            - indicate that source CD was written in TAO mode\n"
"  --tao-source-adjust #   - # of link blocks for TAO source CDs (def. 2)\n"
"  --paranoia-mode #       - DAE paranoia mode (0..3)\n"
"  --with-cddb             - retrieve CDDB CD-TEXT data while copying\n"
"  --cddb-servers <list>   - sets space separated list of CDDB servers\n"
"  --cddb-timeout #        - timeout in seconds for CDDB server communication\n"
"  --cddb-directory <path> - path to local CDDB directory where fetched\n"
"                            CDDB records will be stored\n"
"  --force                 - force execution of operation\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case TOC_INFO:
      log_message(0, "\nUsage: %s toc-info [options] toc-file",
		  options->progName);
    log_message(0,
"options:\n"
"  --tmpdir <path>         - sets directory for temporary wav files\n"
"  --keep                  - keep generated temp wav files after exit\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case TOC_SIZE:
    log_message(0, "\nUsage: %s toc-size [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --tmpdir <path>         - sets directory for temporary wav files\n"
"  --keep                  - keep generated temp wav files after exit\n"
"  -v #                    - sets verbose level\n");
    break;

  case BLANK:
    log_message(0, "\nUsage: %s blank [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --speed <writing-speed> - selects writing speed\n"
"  --blank-mode <mode>     - blank mode ('full', 'minimal')\n"
"  --eject                 - ejects cd after writing or simulation\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case SCAN_BUS:
    log_message(0, "\nUsage: %s scan-bus [-v #]\n", options->progName);
    break;
    
  case UNLOCK:
    log_message(0, "\nUsage: %s unlock [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --reload                - reload the disk if necessary for writing\n"
"  --eject                 - ejects cd after unlocking\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case DRIVE_INFO:
    log_message(0, "\nUsage: %s drive-info [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case COPY_CD:
    log_message(0, "\nUsage: %s copy [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --source-device {<x,y,z>|device} - sets SCSI device of CD-ROM reader\n"
"  --driver <id>           - force usage of specified driver\n"
"  --source-driver <id>    - force usage of specified driver for source device\n"
"  --simulate              - just perform a copy simulation\n"
"  --speed <writing-speed> - selects writing speed\n"
"  --rspeed <read-speed>   - selects reading speed\n"
"  --multi                 - session will not be closed\n"
"  --buffer-under-run-protection #\n"
"                          - 0: disable buffer under run protection\n"
"                            1: enable buffer under run protection (default)\n"
"  --write-speed-control # - 0: disable writing speed control by the drive\n"
"                            1: enable writing speed control (default)\n" 
"  --overburn              - allow to overburn a medium\n"
"  --full-burn             - force burning to the outer disk edge\n"
"                            with '--driver generic-mmc-raw'\n"
"  --capacity <minutes>    - sets disk capacity for '--full-burn'\n"
"                            you must specify this when using blanks bigger\n"
"                            than 80 min. (90,99,etc.)\n"
"                            because they seems like 80 min. blanks\n"
"  --eject                 - ejects cd after writing or simulation\n"
"  --swap                  - swap byte order of audio files\n"
"  --on-the-fly            - perform on-the-fly copy, no image file is created\n"
"  --datafile <filename>   - name of temporary data file\n"
"  --buffers #             - sets fifo buffer size (min. 10)\n"
"  --session #             - select session\n"
"  --fast-toc              - do not extract pre-gaps and index marks\n"
"  --read-subchan <mode>   - defines sub-channel reading mode\n"
"                            <mode> = rw | rw_raw\n"
"  --keepimage             - the image will not be deleted after copy\n"
"  --tao-source            - indicate that source CD was written in TAO mode\n"
"  --tao-source-adjust #   - # of link blocks for TAO source CDs (def. 2)\n"
"  --paranoia-mode #       - DAE paranoia mode (0..3)\n"
"  --reload                - reload the disk if necessary for writing\n"
"  --force                 - force execution of operation\n"
"  --with-cddb             - retrieve CDDB CD-TEXT data while copying\n"
"  --cddb-servers <list>   - sets space separated list of CDDB servers\n"
"  --cddb-timeout #        - timeout in seconds for CDDB server communication\n"
"  --cddb-directory <path> - path to local CDDB directory where fetched\n"
"                            CDDB records will be stored\n"
"  -v #                    - sets verbose level\n"
"  -n                      - no pause before writing\n");
  break;
  
  case READ_CDDB:
    log_message(0, "\nUsage: %s read-cddb [options] toc-file",
		options->progName);
    log_message(0,
"options:\n"
"  --cddb-servers <list>   - sets space separated list of CDDB servers\n"
"  --cddb-timeout #        - timeout in seconds for CDDB server communication\n"
"  --cddb-directory <path> - path to local CDDB directory where fetched\n"
"                            CDDB records will be stored\n"
"  -v #                    - sets verbose level\n");
    break;
    
  case MSINFO:
    log_message(0, "\nUsage: %s msinfo [options]", options->progName);
    log_message(0,
"options:\n"
"  --device [proto:]{<x,y,z>|device} - sets SCSI device of CD-writer\n"
"  --driver <id>           - force usage of specified driver\n"
"  --reload                - reload the disk if necessary for writing\n"
"  -v #                    - sets verbose level\n");
    break;
    
  default:
    log_message(0, "Sorry, no help available for command %d :-(\n",
		options->command);
    break;
  }
  
}

static void importSettings(DaoCommandLine* opts, Settings* settings)
{
    const char *sval;
    const int *ival;

    DaoCommand cmd = opts->command;

    if (cmd == SIMULATE || cmd == WRITE || cmd == COPY_CD) {
	if ((sval = settings->getString(Settings::setWriteDriver)) != NULL) {
	    opts->driverId = strdupCC(sval);
	}

	if ((sval = settings->getString(Settings::setWriteDevice)) != NULL) {
	    opts->scsiDevice = strdupCC(sval);
	}
    
	if ((ival = settings->getInteger(Settings::setWriteSpeed)) != NULL &&
	    *ival >= 0) {
	    opts->writingSpeed = *ival;
	}

	if ((ival = settings->getInteger(Settings::setWriteBuffers)) != NULL &&
	    *ival >= 10) {
	    opts->fifoBuffers = *ival;
	}
	if ((ival = settings->getInteger(Settings::setUserCapacity)) != NULL &&
	    *ival >= 0) {
	    opts->userCapacity = *ival;
	}
	if ((ival = settings->getInteger(Settings::setFullBurn)) != NULL &&
	    *ival >= 0) {
	    opts->fullBurn = *ival;
	}
    }

    if (cmd == READ_CD || cmd == READ_TOC) {
	if ((sval = settings->getString(Settings::setReadDriver)) != NULL) {
	    opts->driverId = strdupCC(sval);
	}

	if ((sval = settings->getString(Settings::setReadDevice)) != NULL) {
	    opts->scsiDevice = strdupCC(sval);
	}

	if ((ival = settings->getInteger(Settings::setReadParanoiaMode)) != NULL &&
	    *ival >= 0) {
	    opts->paranoiaMode = *ival;
	}
    }

    if (cmd == COPY_CD) {
	if ((sval = settings->getString(Settings::setReadDriver)) != NULL) {
	    opts->sourceDriverId = strdupCC(sval);
	}

	if ((sval = settings->getString(Settings::setReadDevice)) != NULL) {
	    opts->sourceScsiDevice = strdupCC(sval);
	}
    
	if ((ival = settings->getInteger(Settings::setReadParanoiaMode)) != NULL &&
	    *ival >= 0) {
	    opts->paranoiaMode = *ival;
	}
    }

    if (cmd == BLANK || cmd == DISK_INFO || cmd == MSINFO || cmd == UNLOCK ||
	cmd == DISCID || cmd == DRIVE_INFO) {
	if ((sval = settings->getString(Settings::setWriteDriver)) != NULL) {
	    opts->driverId = strdupCC(sval);
	}

	if ((sval = settings->getString(Settings::setWriteDevice)) != NULL) {
	    opts->scsiDevice = strdupCC(sval);
	}
    }

    if (cmd == READ_CDDB || cmd == COPY_CD || cmd == READ_TOC ||
	cmd == READ_CD || cmd == DISCID) {
	if ((sval = settings->getString(Settings::setCddbServerList)) != NULL) {
	    opts->cddbServerList = strdupCC(sval);
	}

	if ((sval = settings->getString(Settings::setCddbDbDir)) != NULL) {
	    opts->cddbLocalDbDir = strdupCC(sval);
	}

	if ((ival = settings->getInteger(Settings::setCddbTimeout)) != NULL &&
	    *ival > 0) {
	    opts->cddbTimeout = *ival;
	}
	if ((sval = settings->getString(Settings::setTmpFileDir)) != NULL) {
	    opts->tmpFileDir = strdupCC(sval);
	}
    }

    if ((ival = settings->getInteger(Settings::setReadSpeed)) != NULL &&
	*ival >= 0) {
	opts->readingSpeed = *ival;
    }  
}

static void exportSettings(DaoCommandLine* opts, Settings* settings)
{
    DaoCommand cmd = opts->command;

    if (cmd == SIMULATE || cmd == WRITE || cmd == COPY_CD) {
	if (opts->driverId != NULL)
	    settings->set(Settings::setWriteDriver, opts->driverId);
    
	if (opts->scsiDevice != NULL)
	    settings->set(Settings::setWriteDevice, opts->scsiDevice);

	if (opts->writingSpeed >= 0) {
	    settings->set(Settings::setWriteSpeed, opts->writingSpeed);
    }

	if (opts->fifoBuffers > 0) {
	    settings->set(Settings::setWriteBuffers, opts->fifoBuffers);
	}

	if (opts->fullBurn > 0) {
	    settings->set(Settings::setFullBurn, opts->fullBurn);
	}

	if (opts->userCapacity > 0) {
	    settings->set(Settings::setUserCapacity, opts->userCapacity);
	}
    }

    if (cmd == READ_CD) {
	if (opts->driverId != NULL)
	    settings->set(Settings::setReadDriver, opts->driverId);

	if (opts->scsiDevice != NULL)
	    settings->set(Settings::setReadDevice, opts->scsiDevice);

	settings->set(Settings::setReadParanoiaMode, opts->paranoiaMode);
    }

    if (cmd == COPY_CD) {
	if (opts->sourceDriverId != NULL)
	    settings->set(Settings::setReadDriver, opts->sourceDriverId);

	if (opts->sourceScsiDevice != NULL)
	    settings->set(Settings::setReadDevice, opts->sourceScsiDevice);

	settings->set(Settings::setReadParanoiaMode, opts->paranoiaMode);
    }

    if (cmd == BLANK || cmd == DISK_INFO || cmd == MSINFO || cmd == UNLOCK ||
	cmd == DISCID || cmd == DRIVE_INFO) {
	if (opts->driverId != NULL)
	    settings->set(Settings::setWriteDriver, opts->driverId);
    
	if (opts->scsiDevice != NULL)
	    settings->set(Settings::setWriteDevice, opts->scsiDevice);
    }

    if (cmd == READ_CDDB ||
	(opts->withCddb && (cmd == COPY_CD || cmd == READ_TOC ||
			      cmd == READ_CD || cmd == DISCID))) {
	if (opts->cddbServerList != NULL) {
	    settings->set(Settings::setCddbServerList, opts->cddbServerList);
	}

	if (opts->cddbLocalDbDir != NULL) {
	    settings->set(Settings::setCddbDbDir, opts->cddbLocalDbDir);
	}

	if (opts->cddbTimeout > 0) {
	    settings->set(Settings::setCddbTimeout, opts->cddbTimeout);
	}
    }

    if (opts->readingSpeed >= 0) {
	settings->set(Settings::setReadSpeed, opts->readingSpeed);
    }

    if (cmd == SHOW_TOC || cmd == SIMULATE || cmd == WRITE ||
	cmd == TOC_INFO || cmd == TOC_SIZE) {
	settings->set(Settings::setTmpFileDir, opts->tmpFileDir);
    }
}

static int parseCmdline(int argc, char **argv, DaoCommandLine* opts,
			Settings* settings)
{
    int i;

    if (argc < 1) {
	return 1;
    }

    for (i = 0; i < LAST_CMD; i++) {
	if (strcmp(*argv, cmdInfo[i].str) == 0) {
	    opts->command = cmdInfo[i].cmd;
	    break;
	}
    }

    if (opts->command == UNKNOWN) {
	log_message(-2, "Illegal command: %s", *argv);
	return 1;
    }

    // retrieve settings from $HOME/.cdrdao for given command
    importSettings(opts, settings);

    argc--, argv++;

    while (argc > 0 && (*argv)[0] == '-') {

	if ((*argv)[1] != '-') {
	    switch ((*argv)[1]) {
	    case 'h':
		return 1;
	
	    case 'v':
		if ((*argv)[2] != 0) {
		    opts->verbose = atoi((*argv) + 2);
		} else {
		    if (argc < 2) {
			log_message(-2, "Missing argument after: %s", *argv);
			return 1;
		    }  else {
			opts->verbose = atoi(argv[1]);
			argc--, argv++;
		    }
		}
		break;

	    case 'n':
		opts->pause = false;
		break;

	    default:
		log_message(-2, "Illegal option: %s", *argv);
		return 1;
		break;
	    }
	} 
	else {

	    if (strcmp((*argv) + 2, "help") == 0) {
		return 1;
	    }
	    if (strcmp((*argv) + 2, "device") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->scsiDevice = argv[1];
		    argc--, argv++;
		}
	    } 
	    else if (strcmp((*argv) + 2, "source-device") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->sourceScsiDevice = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "rspeed") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->readingSpeed = atol(argv[1]);
		    if (opts->readingSpeed < 0) {
			log_message(-2, "Illegal reading speed: %s", argv[1]);
			return 1;
		    }
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "speed") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->writingSpeed = atol(argv[1]);
		    if (opts->writingSpeed < 0) {
			log_message(-2, "Illegal writing speed: %s", argv[1]);
			return 1;
		    }
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "capacity") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->userCapacity = atol(argv[1]);
		    if (opts->userCapacity < 0) {
			log_message(-2, "Illegal disk capacity: %s minutes",
				    argv[1]);
			return 1;
		    }
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "blank-mode") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    if (strcmp(argv[1], "full") == 0) {
			opts->blankingMode = CdrDriver::BLANK_FULL;
		    } else if (strcmp(argv[1], "minimal") == 0) {
			opts->blankingMode = CdrDriver::BLANK_MINIMAL;
		    } else {
			log_message(-2, "Illegal blank mode. Valid values: full minimal");
			return 1;
		    }
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "paranoia-mode") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->paranoiaMode= atol(argv[1]);
		    if (opts->paranoiaMode < 0) {
			log_message(-2, "Illegal paranoia mode: %s", argv[1]);
			return 1;
		    }
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "remote") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->remoteFd = atol(argv[1]);
		    if (opts->remoteFd < 0) {
			log_message(-2, "Invalid remote message file descriptor: %s", argv[1]);
			return 1;
		    }
		    opts->remoteMode = true;
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "eject") == 0) {
		opts->eject = true;
	    }
	    else if (strcmp((*argv) + 2, "swap") == 0) {
		opts->swap = true;
	    }
	    else if (strcmp((*argv) + 2, "query-string") == 0) {
		opts->printQuery = true;
	    }
	    else if (strcmp((*argv) + 2, "multi") == 0) {
		opts->multiSession = true;
	    }
	    else if (strcmp((*argv) + 2, "simulate") == 0) {
		opts->writeSimulate = true;
	    }
	    else if (strcmp((*argv) + 2, "fast-toc") == 0) {
		opts->fastToc = true;
	    }
	    else if (strcmp((*argv) + 2, "read-raw") == 0) {
		opts->readRaw = true;
	    }
	    else if (strcmp((*argv) + 2, "no-mode2-mixed") == 0) {
		opts->mode2Mixed = false;
	    }
	    else if (strcmp((*argv) + 2, "reload") == 0) {
		opts->reload = true;
	    }
	    else if (strcmp((*argv) + 2, "force") == 0) {
		opts->force = true;
	    }
	    else if (strcmp((*argv) + 2, "keep") == 0) {
		opts->keep = true;
	    }
	    else if (strcmp((*argv) + 2, "on-the-fly") == 0) {
		opts->onTheFly = true;
	    }
	    else if (strcmp((*argv) + 2, "save") == 0) {
		opts->saveSettings = true;
	    }
	    else if (strcmp((*argv) + 2, "tao-source") == 0) {
		opts->taoSource = true;
	    }
	    else if (strcmp((*argv) + 2, "keepimage") == 0) {
		opts->keepImage = true;
	    }
	    else if (strcmp((*argv) + 2, "overburn") == 0) {
		opts->overburn = true;
	    }
	    else if (strcmp((*argv) + 2, "full-burn") == 0) {
		opts->fullBurn = true;
	    }
	    else if (strcmp((*argv) + 2, "with-cddb") == 0) {
		opts->withCddb = true;
	    }
	    else if (strcmp((*argv) + 2, "driver") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->driverId = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "source-driver") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->sourceDriverId = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "datafile") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->dataFilename = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "buffers") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->fifoBuffers = atoi(argv[1]);
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "session") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->session = atoi(argv[1]);
		    argc--, argv++;
		    if (opts->session < 1) {
			log_message(-2, "Illegal session number: %d",
				    opts->session);
			return 1;
		    }
		}
	    }
	    else if (strcmp((*argv) + 2, "cddb-servers") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->cddbServerList = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "cddb-directory") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->cddbLocalDbDir = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "tmpdir") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->tmpFileDir = argv[1];
		    argc--, argv++;
		}
	    }
	    else if (strcmp((*argv) + 2, "cddb-timeout") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->cddbTimeout = atoi(argv[1]);
		    argc--, argv++;
		    if (opts->cddbTimeout < 1) {
			log_message(-2, "Illegal CDDB timeout: %d",
				    opts->cddbTimeout);
			return 1;
		    }
		}
	    }
	    else if (strcmp((*argv) + 2, "tao-source-adjust") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    opts->taoSourceAdjust = atoi(argv[1]);
		    argc--, argv++;
		    if (opts->taoSourceAdjust < 0 ||
			opts->taoSourceAdjust >= 100) {
			log_message(-2, "Illegal number of TAO link blocks: %d",
				    opts->taoSourceAdjust);
			return 1;
		    }
		}
	    }
	    else if (strcmp((*argv) + 2, "buffer-under-run-protection") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    int val = atoi(argv[1]);
		    argc--, argv++;
		    if (val < 0 || val > 1) {
			log_message(-2, "Illegal value for option --buffer-under-run-protection: %d", val);
			return 1;
		    }
		    opts->bufferUnderrunProtection = val;
		}
	    }
	    else if (strcmp((*argv) + 2, "write-speed-control") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    int val = atoi(argv[1]);
		    argc--, argv++;
		    if (val < 0 || val > 1) {
			log_message(-2, "Illegal value for option --write-speed-control: %d", val);
			return 1;
		    }
		    opts->writeSpeedControl = val;
		}
	    }
	    else if (strcmp((*argv) + 2, "read-subchan") == 0) {
		if (argc < 2) {
		    log_message(-2, "Missing argument after: %s", *argv);
		    return 1;
		} else {
		    if (strcmp(argv[1], "rw") == 0) {
			opts->readSubchanMode = TrackData::SUBCHAN_RW;
		    } else if (strcmp(argv[1], "rw_raw") == 0) {
			opts->readSubchanMode = TrackData::SUBCHAN_RW_RAW;
		    } else {
			log_message(-2, "Invalid argument after %s: %s",
				    argv[0], argv[1]);
			return 1;
		    }

		    argc--, argv++;
		}
	    }
	    else {
		log_message(-2, "Illegal option: %s", *argv);
		return 1;
	    }
	}

	argc--, argv++;
    }

    if (cmdInfo[opts->command].needTocFile) {
	if (argc < 1) {
	    log_message(-2, "Missing toc-file.");
	    return 1;
	} else if (argc > 1) {
	    log_message(-2, "Expecting only one toc-file.");
	    return 1;
	}
	opts->tocFile = *argv;
    }


    return 0;
}

// Commit settings to overall system. Export them.
static void commitSettings(DaoCommandLine* opts, Settings* settings,
			   const char* settingsPath)
{
    if (opts->tmpFileDir)
	tempFileManager.setTempDirectory(opts->tmpFileDir);

    tempFileManager.setKeepTemps(opts->keep);

    if (opts->saveSettings && settingsPath != NULL) {
	// If we're saving our settings, give up root privileges and
	// exit. The --save option is only compiled in if setreuid() is
	// available (because it could be used for a local root exploit).
	if (giveUpRootPrivileges()) {
	    exportSettings(opts, settings);
	    settings->write(settingsPath);
	}
	exit(0);
    }
}

// Selects driver for device of 'scsiIf'.
static CdrDriver *selectDriver(DaoCommand cmd, ScsiIf *scsiIf,
			       const char *driverId)
{
  unsigned long options = 0;
  CdrDriver *ret = NULL;

  if (driverId != NULL) {
    char *s = strdupCC(driverId);
    char *p = strchr(s, ':');

    if (p != NULL) {
      *p = 0;
      options = strtoul(p + 1, NULL, 0);
    }

    ret = CdrDriver::createDriver(s, options, scsiIf);

    if (ret == NULL) {
      log_message(-2, "%s: Illegal driver ID, available drivers:", s);
      CdrDriver::printDriverIds();
    }

    delete[] s;
  }
  else {
    const char *id = NULL;
    
    // for reading commands try to select a special read driver first:
    if (cmd == READ_TOC || cmd == READ_CD)
      id = CdrDriver::selectDriver(0, scsiIf->vendor(), scsiIf->product(),
				   &options);

    // try to select a write driver
    if (id == NULL)
      id = CdrDriver::selectDriver(1, scsiIf->vendor(), scsiIf->product(),
				   &options);
    // if no driver is selected, yet, try to select a read driver for
    // disk-info
    if (id == NULL && (cmd == DISK_INFO || cmd == MSINFO || cmd == DISCID))
      id = CdrDriver::selectDriver(0, scsiIf->vendor(), scsiIf->product(),
				   &options);
    // Still no driver, try to autodetect one
    if (id == NULL)
      id = CdrDriver::detectDriver(scsiIf, &options);
      
    if (id != NULL) {
      ret = CdrDriver::createDriver(id, options, scsiIf);
    }
    else {
      log_message(0, "");
      log_message(-2, "No driver found for '%s %s', available drivers:\n",
	      scsiIf->vendor(), scsiIf->product());
      CdrDriver::printDriverIds();

      log_message(0, "\nFor all recent recorder models either the 'generic-mmc' or");
      log_message(0, "the 'generic-mmc-raw' driver should work.");
      log_message(0, "Use option '--driver' to force usage of a driver, e.g.: --driver generic-mmc");
    }
  }

  return ret;
}

const char* getDefaultDevice(DaoDeviceType req)
{
    int i, len;
    static char buf[128];

    // This function should not be called if the command issues
    // doesn't actually require a device.
    assert(req != NO_DEVICE);

    ScsiIf::ScanData* sdata = ScsiIf::scan(&len);

    if (sdata) {
	for (i = 0; i < len; i++) {

	    ScsiIf testif(sdata[i].dev.c_str());

	    if (testif.init() != 0) {
		continue;
	    }
	    bool rr, rw, rwr, rww;

	    if (!testif.checkMmc(&rr, &rw, &rwr, &rww))
		continue;

	    if (req == NEED_CDR_R && !rr)
	      continue;
	    if (req == NEED_CDR_W && !rw)
	      continue;
	    if (req == NEED_CDRW_W && !rww)
	      continue;

	    strncpy(buf, sdata[i].dev.c_str(), 128);
	    delete[] sdata;
	    return buf;
	}
	delete[] sdata;
    }

    return NULL;
}

#define MAX_RETRY 10
static CdrDriver *setupDevice(DaoCommand cmd, const char *scsiDevice,
			      const char *driverId, int initDevice,
			      int checkReady, int checkEmpty,
			      int readingSpeed,
			      bool remote, bool reload)
{
  ScsiIf *scsiIf = NULL;
  CdrDriver *cdr = NULL;
  DiskInfo *di = NULL;
  int inquiryFailed = 0;
  int retry = 0;
  int initTries = 2;
  int ret = 0;

  scsiIf = new ScsiIf(scsiDevice);

  switch (scsiIf->init()) {
  case 1:
    log_message(-2, "Please use option '--device {[proto:]bus,id,lun}|device'"
	    ", e.g. "
            "--device 0,6,0, --device ATA:0,0,0 or --device /dev/cdrom");
    delete scsiIf;
    return NULL;
    break;
  case 2:
    inquiryFailed = 1;
    break;
  }
  
  log_message(2, "%s: %s %s\tRev: %s", scsiDevice, scsiIf->vendor(),
	  scsiIf->product(), scsiIf->revision());


  if (inquiryFailed && driverId == NULL) {
    log_message(-2, "Inquiry failed and no driver id is specified.");
    log_message(-2, "Please use option --driver to specify a driver id.");
    delete scsiIf;
    return NULL;
  }

  if ((cdr = selectDriver(cmd, scsiIf, driverId)) == NULL) {
    delete scsiIf;
    return NULL;
  }

  log_message(2, "Using driver: %s (options 0x%04lx)\n", cdr->driverName(),
	  cdr->options());

  if (!initDevice)
    return cdr;
      
  while (initTries > 0) {
    // clear unit attention
    cdr->rezeroUnit(0);
    if (readingSpeed >= 0) {
      if (!cdr->rspeed(readingSpeed)) {
        log_message(-2, "Reading speed %d is not supported by device.",
                readingSpeed);
        exit(1);
      }
    }

    if (checkReady) {
      retry = 0;

      while (retry < MAX_RETRY) {
	if (retry++)
	  sleep(3);
	if (!(ret = cdr->testUnitReady(1)))
	  break;
	if (ret == 1) {
	  delete cdr;
	  delete scsiIf;
	  return NULL;
	}
	log_message(-1, "Unit not ready, still trying...");
      }

      if (ret != 0) {
	log_message(-2, "Unit not ready, giving up.");
	delete cdr;
	delete scsiIf;
	return NULL;
      }
	
      cdr->rezeroUnit(0);

      if (readingSpeed >= 0) {
        log_message(0, "Setting reading speed %d.",
                readingSpeed); 
        if (!cdr->rspeed(readingSpeed)) {
          log_message(-2, "Reading speed %d is not supported by device.",
                  readingSpeed);
          exit(1);
        }
      }

      if ((di = cdr->diskInfo()) == NULL) {
	log_message(-2, "Cannot get disk information.");
	delete cdr;
	delete scsiIf;
	return NULL;
      }

      if (checkEmpty && initTries == 2 &&
	  di->valid.empty && !di->empty && 
	  (!di->valid.append || !di->append) &&
	  (!remote || reload)) {
	if (!reload) {
	  log_message(0, "Disk seems to be written - hit return to reload disk.");
	  fgetc(stdin);
	}

	log_message(1, "Reloading disk...");

	if (cdr->loadUnload(1) != 0) {
	  delete cdr;
	  delete scsiIf;
	  return NULL;
	}

	sleep(1);
	cdr->rezeroUnit(0); // clear unit attention

	if (cdr->loadUnload(0) != 0) {
	  log_message(-2, "Cannot load tray.");
	  delete cdr;
	  delete scsiIf;
	  return NULL;
	}

	initTries = 1;
      }
      else {
	initTries = 0;
      }
    }
    else {
      initTries = 0;
    }
  }

#ifdef __CYGWIN__
/* 	Experimental device locking code. Should work on Win2k/NT only.  */
	typedef struct _SCSI_ADDRESS {
		ULONG Length;
		UCHAR PortNumber;
		UCHAR PathId;
		UCHAR TargetId;
		UCHAR Lun;
	}SCSI_ADDRESS, *PSCSI_ADDRESS;

	OSVERSIONINFO osinfo;
	osinfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
	if ((GetVersionEx (&osinfo)) && (osinfo.dwPlatformId == VER_PLATFORM_WIN32_NT))
		isNT = true;
	if (isNT)	{
		char devletter;
		SCSI_ADDRESS sa;
		DWORD bytes;
		bool gotit = false;
		int ha,id,lun;

		ha = scsiIf->bus ();
		id = scsiIf->id ();
		lun = scsiIf->lun ();

		for (devletter = 'A'; devletter <= 'Z'; devletter++)	{
			sprintf (devstr, "%c:\\\0", devletter);
			if (GetDriveType (devstr) != DRIVE_CDROM)
				continue;
			sprintf (devstr, "\\\\.\\%c:", devletter);
			fh = CreateFile (devstr,
				GENERIC_READ,
				0,
				NULL,
				OPEN_EXISTING,
				FILE_FLAG_WRITE_THROUGH|FILE_FLAG_NO_BUFFERING,
				NULL);
			if (fh == INVALID_HANDLE_VALUE)	{
				//~ printf ("Error opening device %s: %d\n", devstr, GetLastError());
				fh = NULL;
				continue;
			}
			if (DeviceIoControl (fh, IOCTL_SCSI_GET_ADDRESS, NULL, 0, &sa, sizeof(SCSI_ADDRESS), &bytes, NULL))	{
				if ( (ha == sa.PortNumber) && (lun == sa.Lun) && (id == sa.TargetId) )	{
					gotit = true;
					break;
				}	else	{
					CloseHandle (fh);
					fh = NULL;
					continue;
				}
			}
		}
		if (gotit)	{
			if (!DeviceIoControl (fh, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL))	{
				log_message(-2, "Couldn't lock device %s!", devstr);
				CloseHandle (fh);
				fh = NULL;
			}
			else
				log_message(2, "OS lock on device %s. Unit won't be accessible while burning.", devstr);
		}	else	{
			log_message(-2, "Unable to determine drive letter for device %s! No OS level locking.", scsiDevice);
			if (fh) CloseHandle (fh);
			fh = NULL;
		}
	}	else
		log_message(2,"You are running Windows 9x. No OS level locking available.");
#endif

  if (readingSpeed >= 0) {
    if (!cdr->rspeed(readingSpeed)) {
      log_message(-2, "Reading speed %d is not supported by device.",
              readingSpeed);
      exit(1);
    }
  }

  return cdr;
}

static void showDriveInfo(const DriveInfo *i)
{
  if (i == NULL) {
    log_message(0, "No drive information available.");
    return;
  }

  printf("Maximum reading speed: %d kB/s\n", i->maxReadSpeed);
  printf("Current reading speed: %d kB/s\n", i->currentReadSpeed);
  printf("Maximum writing speed: %d kB/s\n", i->maxWriteSpeed);
  printf("Current writing speed: %d kB/s\n", i->currentWriteSpeed);
  printf("BurnProof supported: %s\n", i->burnProof ? "yes" : "no");
  printf("JustLink supported: %s\n", i->ricohJustLink ? "yes" : "no");
  printf("JustSpeed supported: %s\n", i->ricohJustSpeed ? "yes" : "no");
}

static void showTocInfo(const Toc *toc, const char *tocFile)
{
  long len = toc->length().lba() * AUDIO_BLOCK_LEN;
  len >>= 20;

  printf("%s: %d tracks, length %s, %ld blocks, %ld MB\n", tocFile,
	 toc->nofTracks(), toc->length().str(), toc->length().lba(), len);
}

static void showTocSize(const Toc *toc, const char *tocFile)
{
  printf("%ld\n", toc->length().lba());
}

static void showToc(const Toc *toc)
{
  const Track *t;
  Msf start, end, index;
  int i;
  int n;
  int tcount = 1;
  char buf[14];

  printf("TOC TYPE: %s\n", Toc::tocType2String(toc->tocType()));

  if (toc->catalogValid()) {
    for (i = 0; i < 13; i++) 
      buf[i] = toc->catalog(i) + '0';
    buf[13] = 0;

    printf("CATALOG NUMBER: %s\n", buf);
  }

  TrackIterator itr(toc);

  for (t = itr.first(start, end); t != NULL; t = itr.next(start, end)) {
    if (tcount > 1)
      printf("\n");

    printf("TRACK %2d  Mode %s", tcount,
	    TrackData::mode2String(t->type()));

    if (t->subChannelType() != TrackData::SUBCHAN_NONE)
      printf(" %s", TrackData::subChannelMode2String(t->subChannelType()));

    printf(":\n");

    if (t->type() == TrackData::AUDIO) {
      if (t->isrcValid()) {
	printf("          ISRC %c%c %c%c%c %c%c %c%c%c%c%c\n",
		t->isrcCountry(0), t->isrcCountry(1),
		t->isrcOwner(0), t->isrcOwner(1), t->isrcOwner(2),
		t->isrcYear(0) + '0', t->isrcYear(1) + '0',
		t->isrcSerial(0) + '0', t->isrcSerial(1) + '0',
		t->isrcSerial(2) + '0', t->isrcSerial(3) + '0',
		t->isrcSerial(4) + '0');
      }
    }
    printf("          COPY%sPERMITTED\n",
	    t->copyPermitted() ? " " : " NOT ");

    if (t->type() == TrackData::AUDIO) {
      printf("          %sPRE-EMPHASIS\n",
	      t->preEmphasis() ? "" : "NO ");
      printf("          %s CHANNEL AUDIO\n",
	      t->audioType() == 0 ? "TWO" : "FOUR");
    }

    if (t->start().lba() != 0) {
      printf("          PREGAP %s(%6ld)\n", 
	      t->start().str(), t->start().lba());
    }
    printf("          START  %s(%6ld)\n",
	    start.str(), start.lba());
    n = t->nofIndices();
    for (i = 0; i < n; i++) {
      index = start + t->getIndex(i);
      printf("          INDEX %2d %s(%6ld)\n",
	      i + 2, index.str(), index.lba());
    }

    printf("          END%c   %s(%6ld)\n",
	    t->isPadded() ? '*' : ' ', end.str(), end.lba());

    tcount++;
  }
} 

void showData(const Toc *toc, bool swap)
{
  long length = toc->length().lba();
  Sample buf[SAMPLES_PER_BLOCK];
  int i;
  unsigned long sampleNr = 0;
  long lba = 150;

  TocReader reader(toc);

  if (reader.openData() != 0) {
    log_message(-2, "Cannot open audio data.");
    return;
  }

  while (length > 0) {
    if (reader.readSamples(buf, SAMPLES_PER_BLOCK) != SAMPLES_PER_BLOCK) {
      log_message(-2, "Read of audio data failed.");
      return;
    }
    lba++;

    if (swap) {
      swapSamples(buf, SAMPLES_PER_BLOCK);
    }

    for (i = 0; i < SAMPLES_PER_BLOCK; i++) {
      printf("%7lu:%6d %6d\n", sampleNr, buf[i].left(), buf[i].right());
      sampleNr++;
    }
    length -= 1;
  }
}

void showDiskInfo(DiskInfo *di)
{
  const char *s1, *s2;

  log_message(2, "That data below may not reflect the real status of the inserted medium");
  log_message(2, "if a simulation run was performed before. Reload the medium in this case.");
  log_message(2, "");

  printf("CD-RW                : ");
  if (di->valid.cdrw)
    printf(di->cdrw ? "yes" : "no");
  else 
    printf("n/a");

  printf("\n");

  printf("Total Capacity       : ");
  if (di->valid.capacity)
    printf("%s (%ld blocks, %ld/%ld MB)", Msf(di->capacity).str(),
	   di->capacity,
	   (di->capacity * 2) >> 10,
	   (di->capacity * AUDIO_BLOCK_LEN) >> 20);
  else
    printf("n/a");

  printf("\n");

  printf("CD-R medium          : ");
  if (di->valid.manufacturerId) {
    if (CdrDriver::cdrVendor(di->manufacturerId, &s1, &s2)) {
      printf("%s\n", s1);
      printf("                       %s\n", s2);
    }
    else {
      printf("%s: unknown vendor ID\n", di->manufacturerId.str());
    }
  }
  else {
    printf("n/a\n");
  }

  printf("Recording Speed      : ");
  if (di->valid.recSpeed)
    printf("%dX - %dX", di->recSpeedLow, di->recSpeedHigh);
  else
    printf("n/a");

  printf("\n");

  printf("CD-R empty           : ");
  if (di->valid.empty)
    printf(di->empty ? "yes" : "no");
  else 
    printf("n/a");

  printf("\n");

  if (di->valid.empty && !di->empty && di->valid.append) {
    printf("Toc Type             : ");
    switch (di->diskTocType) {
    case 0x00:
      printf("CD-DA or CD-ROM");
      break;
    case 0x10:
      printf("CD-I");
      break;
    case 0x20:
      printf("CD-ROM XA");
      break;
    case 0xff:
      printf("Undefined");
      break;
    default:
      printf("invalid: %d", di->diskTocType);
      break;
    }

    printf("\n");

    printf("Sessions             : %d\n", di->sessionCnt);
    printf("Last Track           : %d\n", di->lastTrackNr);
    printf("Appendable           : %s\n", di->append ? "yes" : "no");

    if (di->append) {
      printf("Start of last session: %ld (%s)\n", di->lastSessionLba,
	      Msf(di->lastSessionLba + 150).str());
      printf("Start of new session : %ld (%s)\n", di->thisSessionLba,
	      Msf(di->thisSessionLba + 150).str());

      if (di->valid.capacity && di->capacity > di->thisSessionLba) {
	long r = di->capacity - di->thisSessionLba;

	printf("Remaining Capacity   : %s (%ld blocks, %ld/%ld MB)\n",
		Msf(r).str(), r, (r * 2) >> 10, (r * AUDIO_BLOCK_LEN) >> 20);
      }
    }
  }
}

/*
 * Show multi session info in a format that is easy to parse with scritps.
 * Return: 0: OK
 *         1: disk is not empty and not appendable
 *         2: could not determine the requested information
 */
static int showMultiSessionInfo(DiskInfo *di)
{
  
  if (di->valid.empty) {
    if (di->empty) {
      // print nothing  to indicate empty disk
      return 0;
    }
    else if (di->valid.append) {
      if (di->append) {
	printf("%ld,%ld\n", di->lastSessionLba, di->thisSessionLba);
	return 0;
      }
      else {
	return 1;
      }
    }
  }

  return 2;
}

static void printCddbQuery(Toc *toc)
{
  Cddb cddb(toc);

  cddb.printDbQuery();
}

static int readCddb(DaoCommandLine* opts, Toc *toc, bool showEntry = false)
{
  int err = 0;
  char *servers = strdupCC(opts->cddbServerList);
  char *p;
  const char *sep = " ,";
  char *user = NULL;
  char *host = NULL;
  struct passwd *pwent;
  Cddb::QueryResults *qres, *qrun, *qsel;
  Cddb::CddbEntry *dbEntry;

  Cddb cddb(toc);

  cddb.timeout(opts->cddbTimeout);

  if (opts->cddbLocalDbDir != NULL)
    cddb.localCddbDirectory(opts->cddbLocalDbDir);


  for (p = strtok(servers, sep); p != NULL; p = strtok(NULL, sep))
    cddb.appendServer(p);

  delete[] servers;
  servers = NULL;

  if ((pwent = getpwuid(getuid())) != NULL &&
      pwent->pw_name != NULL) {
    user = strdupCC(pwent->pw_name);
  }
  else {
    user = strdupCC("unknown");
  }

  {
    struct utsname sinfo;
    if (uname(&sinfo) == 0) {
      host = strdupCC(sinfo.nodename);
    }
    else {
      host = strdupCC("unknown");
    }
  }
  

  if (cddb.connectDb(user, host, "cdrdao", VERSION) != 0) {
    log_message(-2, "Cannot connect to any CDDB server.");
    err = 2; goto fail;
  }

	
  if (cddb.queryDb(&qres) != 0) {
    log_message(-2, "Querying of CDDB server failed.");
    err = 2; goto fail;
  }
  
  if (qres == NULL) {
    log_message(1, "No CDDB record found for this toc-file.");
    err = 1; goto fail;
  }

  if (qres->next != NULL || !(qres->exactMatch)) {
    int qcount;

    if (qres->next == NULL)
      log_message(0, "Found following inexact match:");
    else
      log_message(0, "Found following inexact matches:");
    
    log_message(0, "\n    DISKID   CATEGORY     TITLE\n");
    
    for (qrun = qres, qcount = 0; qrun != NULL; qrun = qrun->next, qcount++) {
      log_message(0, "%2d. %-8s %-12s %s", qcount + 1, qrun->diskId,
	      qrun->category,  qrun->title);
    }

    log_message(0, "\n");

    qsel = NULL;

    while (1) {
      char buf[20];
      int sel;

      log_message(0, "Select match, 0 for none [0-%d]?", qcount);

      if (fgets(buf, 20, stdin) == NULL)
	break;

      for (p = buf; *p != 0 && isspace(*p); p++) ;

      if (*p != 0 && isdigit(*p)) {
	sel = atoi(p);

	if (sel == 0) {
	  break;
	}
	else if (sel > 0 && sel <= qcount) {
	  sel--;
	  for (qsel = qres, qcount = 0; qsel != NULL && qcount != sel;
	       qsel = qsel->next, qcount++) ;

	  break;
	}
      }
    }

    if (qsel == NULL) {
      log_message(0, "No match selected.");
      err = 1; goto fail;
    }
  }
  else {
    qsel = qres;
  }


  log_message(1, "Reading CDDB record for: %s-%s-%s", qsel->diskId, qsel->category,
	  qsel->title);

  if (cddb.readDb(qsel->category, qsel->diskId, &dbEntry) != 0) {
    log_message(-2, "Reading of CDDB record failed.");
    err = 2; goto fail;
  }

  if (showEntry)
      cddb.printDbEntry();

  if (!cddb.addAsCdText(toc))
    err = 1;

 fail:
  delete[] user;
  delete[] host;

  return err;
}

static void scanBus()
{
  int i, len;
  ScsiIf::ScanData *sdata = ScsiIf::scan(&len);

  if (sdata) {
    for (i = 0; i < len; i++) {
      log_message(0, "%s : %s, %s, %s", sdata[i].dev.c_str(), sdata[i].vendor,
              sdata[i].product, sdata[i].revision);
    }
    delete[] sdata;
  }

#ifdef SCSI_ATAPI
  sdata = ScsiIf::scan(&len, "ATA");
  if (sdata) {
    for (i = 0; i < len; i++) {
      log_message(0, "%-20s %s, %s, %s", sdata[i].dev.c_str(), sdata[i].vendor,
              sdata[i].product, sdata[i].revision);
    }
    delete[] sdata;
  } else {
    sdata = ScsiIf::scan(&len, "ATAPI");
    if (sdata) {
      for (i = 0; i < len; i++) {
        log_message(0, "%-20s %s, %s, %s", sdata[i].dev.c_str(), sdata[i].vendor,
                sdata[i].product, sdata[i].revision);
      }
      delete[] sdata;
    }
  }
#endif
}

static int checkToc(const Toc *toc, bool force)
{
    int ret = toc->check();

    if (ret == 0 || (ret == 1 && force))
	return 0;

    log_message(-2, "The toc check function detected at least one warning.");
    log_message(-2, "If you record this toc the resulting CD might be unusable");
    log_message(-2, "or the recording process might abort with error.");
    log_message(-2, "Use option --force to ignore the warnings.");

    return ret;
}

static int copyCd(DaoCommandLine* opts, CdrDriver *src, CdrDriver *dst)
{
    char dataFilenameBuf[50];
    long pid = getpid();
    Toc *toc;
    int ret = 0;
    DiskInfo *di = NULL;
    int isAppendable = 0;

    if (opts->dataFilename == NULL) {
	// create a unique temporary data file name in current directory
	sprintf(dataFilenameBuf, "cddata%ld.bin", pid);
	opts->dataFilename = dataFilenameBuf;
    }

    src->rawDataReading(true);
    src->taoSource(opts->taoSource);
    if (opts->taoSourceAdjust >= 0)
	src->taoSourceAdjust(opts->taoSourceAdjust);

    if ((toc = src->readDisk(opts->session, opts->dataFilename)) == NULL) {
	unlink(opts->dataFilename);
	log_message(-2, "Creation of source CD image failed.");
	return 1;
    }

    if (opts->withCddb) {
	if (readCddb(opts, toc) == 0)
	    log_message(2, "CD-TEXT data was added to toc-file.");
	else
	    log_message(2, "Reading of CD-TEXT data failed - "
			"continuing without CD-TEXT data.");
    }

    if (opts->keepImage) {
	char tocFilename[50];

	sprintf(tocFilename, "cd%ld.toc", pid);
    
	log_message(2, "Keeping created image file \"%s\".",
		    opts->dataFilename);
	log_message(2, "Corresponding toc-file is written to \"%s\".",
		    tocFilename);

	toc->write(tocFilename);
    }

    if (checkToc(toc, opts->force)) {
	log_message(-3, "Toc created from source CD image is inconsistent.");
	toc->print(std::cout);
	delete toc;
	return 1;
    }

    switch (dst->checkToc(toc)) {
    case 0: // OK
	break;
    case 1: // warning
	if (!opts->force) {
	    log_message(-2, "The toc extracted from the source CD is not suitable for");
	    log_message(-2, "the recorder device and may produce undefined results.");
	    log_message(-2, "Use option --force to use it anyway.");
	    delete toc;
	    return 1;
	}
	break;
    default: // error
	log_message(-2, "The toc extracted from the source CD is not suitable for this drive.");
	delete toc;
	return 1;
	break;
    }

    if (src == dst) {
	// Unlock src to make swaping possible
	src->preventMediumRemoval(0);
	log_message(0, "Please insert a recordable medium and hit enter.");
	getc(stdin);
    }

    do {
	if ((di = dst->diskInfo()) == NULL) {
	    log_message(-2, "Cannot get disk information from recorder device.");
	    delete toc;
	    return 1;
	}

	if (!di->valid.empty) {
	    log_message(-2, "Cannot determine disk status - hit enter to try again.");
	    getc(stdin);
	    isAppendable = 0;
	} else if (!di->empty && (!di->valid.append || !di->append)) {
	    log_message(0, "Medium in recorder device is not empty and not appendable.");
	    log_message(0, "Please insert a recordable medium and hit enter.");
	    getc(stdin);
	    isAppendable = 0;
	} else {
	    isAppendable = 1;
	}
    } while (!isAppendable);


    if (dst->preventMediumRemoval(1) != 0) {
	if (!opts->keepImage) {
	    if (unlink(opts->dataFilename) != 0)
		log_message(-2, "Cannot remove CD image file \"%s\": %s",
			    opts->dataFilename,
			    strerror(errno));
	}

	delete toc;
	return 1;
    }
    
    if (writeDiskAtOnce(toc, dst, opts->fifoBuffers, opts->swap, 0, 0) != 0) {
	if (dst->simulate())
	    log_message(-2, "Simulation failed.");
	else
	    log_message(-2, "Writing failed.");

	ret = 1;
    } else {
	if (dst->simulate())
	    log_message(1, "Simulation finished successfully.");
	else
	    log_message(1, "Writing finished successfully.");
    }

    if (dst->preventMediumRemoval(0) != 0)
	ret = 1;

    dst->rezeroUnit(0);

    if (ret == 0 && opts->eject) {
	dst->loadUnload(1);
    }

    if (!opts->keepImage) {
	if (unlink(opts->dataFilename) != 0)
	    log_message(-2, "Cannot remove CD image file \"%s\": %s",
			opts->dataFilename,
			strerror(errno));
    }

    delete toc;

    return ret;
}

static int copyCdOnTheFly(DaoCommandLine* opts,CdrDriver *src, CdrDriver *dst)
{
    Toc *toc = NULL;
    int pipeFds[2];
    pid_t pid = -1;
    int ret = 0;
    int oldStdin = -1;

    if (src == dst)
	return 1;

    if (pipe(pipeFds) != 0) {
	log_message(-2, "Cannot create pipe: %s", strerror(errno));
	return 1;
    }
  
    src->rawDataReading(true);
    src->taoSource(opts->taoSource);
    if (opts->taoSourceAdjust >= 0)
	src->taoSourceAdjust(opts->taoSourceAdjust);

    src->onTheFly(1);
    // the fd is not used by 'readDiskToc', just need to
    // indicate that on-the-fly copying is active for
    // automatical selection if the first track's pre-gap
    // is padded with zeros in the created Toc.

    if ((toc = src->readDiskToc(opts->session, "-")) == NULL) {
	log_message(-2, "Creation of source CD toc failed.");
	ret = 1;
	goto fail;
    }

    if (opts->withCddb) {
	if (readCddb(opts, toc) != 0) {
	    ret = 1;
	    goto fail;
	} else {
	    log_message(2, "CD-TEXT data was added to toc.");
	}
    }
  
    if (checkToc(toc, opts->force) != 0) {
	log_message(-3, "Toc created from source CD image is inconsistent"
		    "- please report.");
	toc->print(std::cout);
	ret = 1;
	goto fail;
    }

    switch (dst->checkToc(toc)) {
    case 0: // OK
	break;
    case 1: // warning
	if (!opts->force) {
	    log_message(-2, "The toc extracted from the source CD is not suitable for");
	    log_message(-2, "the recorder device and may produce undefined results.");
	    log_message(-2, "Use option --force to use it anyway.");
	    ret = 1;
	    goto fail;
	}
	break;
    default: // error
	log_message(-2, "The toc extracted from the source CD is not suitable for this drive.");
	ret = 1;
	goto fail;
	break;
    }

    if ((pid = fork()) < 0) {
	log_message(-2, "Cannot fork reader process: %s", strerror(errno));
	ret = 1;
	goto fail;
    }

    if (pid == 0) {
	// we are the reader process
	setsid();
	close(pipeFds[0]);

	src->onTheFly(pipeFds[1]);

	opts->verbose = 0;

#ifdef __CYGWIN__
	/* Close the SCSI interface and open it again. This will re-init the
	 * ASPI layer which is required for the child process
	 */

	delete src->scsiIf();

	src->scsiIf(new ScsiIf(opts->sourceScsiDevice));
    
	if (src->scsiIf()->init() != 0) {
	    log_message(-2, "Re-init of SCSI interace failed.");

	    // indicate end of data
	    close(pipeFds[1]);

	    while (1)
		sleep(10);
	}    
#endif

	if (src->readDisk(opts->session, "-") != NULL)
	    log_message(1, "CD image reading finished successfully.");
	else
	    log_message(-2, "CD image reading failed.");

	// indicate end of data
	close(pipeFds[1]);
	while (1)
	    sleep(10);
    }

    close(pipeFds[1]);
    pipeFds[1] = -1;

    if ((oldStdin = dup(fileno(stdin))) < 0) {
	log_message(-2, "Cannot duplicate stdin: %s", strerror(errno));
	ret = 1;
	goto fail;
    }

    if (dup2(pipeFds[0], fileno(stdin)) != 0) {
	log_message(-2, "Cannot connect pipe to stdin: %s", strerror(errno));
	close(oldStdin);
	oldStdin = -1;
	ret = 1;
	goto fail;
    }

    dst->onTheFly(fileno(stdin));

    if (dst->preventMediumRemoval(1) != 0) {
	ret = 1;
	goto fail;
    }

    if (writeDiskAtOnce(toc, dst, opts->fifoBuffers, opts->swap, 0, 0) != 0) {
	if (dst->simulate())
	    log_message(-2, "Simulation failed.");
	else
	    log_message(-2, "Writing failed.");

	ret = 1;
    } else {
	if (dst->simulate())
	    log_message(1, "Simulation finished successfully.");
	else
	    log_message(1, "Writing finished successfully.");
    }

    dst->rezeroUnit(0);

    if (dst->preventMediumRemoval(0) != 0)
	ret = 1;

    if (ret == 0 && opts->eject) {
	dst->loadUnload(1);
    }


fail:
    if (pid > 0) {
	int status;
	kill(pid, SIGKILL);
	wait(&status);
    }

    if (oldStdin >= 0) {
	dup2(oldStdin, fileno(stdin));
	close(oldStdin);
    }

    delete toc;

    close(pipeFds[0]);

    if (pipeFds[1] >= 0)
	close(pipeFds[1]);

    return ret;
}

int main(int argc, char **argv)
{
    int exitCode = 0;
    Toc *toc = NULL;
    ScsiIf *cdrScsi = NULL;
    ScsiIf *srcCdrScsi = NULL;
    CdrDriver *cdr = NULL;
    CdrDriver *srcCdr = NULL;
    int delSrcDevice = 0;
    DiskInfo *di = NULL;
    DiskInfo *srcDi = NULL;
    const char *homeDir;
    const char *settingsPath = NULL;

#if defined(HAVE_SETEUID) && defined(HAVE_SETEGID)
    if (geteuid() == 0 && getuid() != 0) {
	uid_t uid = getuid();
	if (setuid(uid) == -1) {
	    log_message(-2, "Failed to drop privileges; exiting.");
	    exit(1);
        }
    }
#endif

    log_init();

    // Initialize command line options to default values
    DaoCommandLine options;
    setOptionDefaults(&options);
    options.progName = argv[0];

    Settings* settings = new Settings;

    settingsPath = "/etc/cdrdao.conf";
    if (settings->read(settingsPath) == 0)
	log_message(3, "Read settings from \"%s\".", settingsPath);

    settingsPath = "/etc/defaults/cdrdao";
    if (settings->read(settingsPath) == 0)
	log_message(3, "Read settings from \"%s\".", settingsPath);

    settingsPath = NULL;

    if ((homeDir = getenv("HOME")) != NULL) {
	settingsPath = strdup3CC(homeDir, "/.cdrdao", NULL);

	if (settings->read(settingsPath) == 0)
	    log_message(3, "Read settings from \"%s\".", settingsPath);
    } else {
	log_message(-1, "Environment variable 'HOME' not defined"
		    "- cannot read .cdrdao.");
    }

#ifdef UNIXWARE
    if (getuid() != 0) {
	log_message(-2, "You must be root to use cdrdao.");
	exit(1);
    }
#endif

    // Parse command line command and options.
    if (parseCmdline(argc - 1, argv + 1, &options, settings) != 0) {
	log_set_verbose(2);
	printVersion();
	printUsage(&options);
	exit(1);
    }

    log_set_verbose(options.verbose);
    commitSettings(&options, settings, settingsPath);

    printVersion();

    // Just show version ? We're done.
    if (options.command == SHOW_VERSION)
	goto fail;

    // ---------------------------------------------------------------------
    //   Parse and check the toc file
    // ---------------------------------------------------------------------
    if (cmdInfo[options.command].tocParse) {

	// Parse TOC file
	toc = Toc::read(options.tocFile);

	if (options.remoteMode) {
	    unlink(options.tocFile);
	}

	// Check and resolve input files paths
	if (!toc || !toc->resolveFilenames(options.tocFile)) {
	    exitCode = 1;
	    goto fail;
	}

	if (!toc->convertFilesToWav()) {
	    log_message(-2,
			"Could not decode audio files from toc file \"%s\".",
			options.tocFile);
	    exitCode = 1;
	    goto fail;
	}

	toc->recomputeLength();

	if (cmdInfo[options.command].tocCheck) {
	    if (checkToc(toc, options.force) != 0) {
		log_message(-2, "Toc file \"%s\" is inconsistent.",
			    options.tocFile);
		exitCode = 1;
		goto fail;
	    }
	}
    }

    // ---------------------------------------------------------------------
    //   Setup the CD device, obtain disk media information.
    // ---------------------------------------------------------------------

    if (cmdInfo[options.command].requiredDevice != NO_DEVICE) {

	if (!options.scsiDevice)
	    options.scsiDevice =
		getDefaultDevice(cmdInfo[options.command].requiredDevice);

	cdr = setupDevice(options.command,
			  options.scsiDevice,
			  options.driverId, 
			  /* init device? */
			  (options.command == UNLOCK) ? 0 : 1,
			  /* check for ready status? */
			  (options.command == BLANK ||
			   options.command == DRIVE_INFO ||
			   options.command == DISCID) ? 0 : 1,
			  /* reset status of medium if not empty? */
			  (options.command == SIMULATE ||
			   options.command == WRITE) ? 1 : 0,
			  options.readingSpeed,
			  options.remoteMode,
			  options.reload);

	if (cdr == NULL) {
	    log_message(-2, "Cannot setup device %s.", options.scsiDevice);
	    exitCode = 1; goto fail;
	}

	cdrScsi = cdr->scsiIf();

	if ((di = cdr->diskInfo()) == NULL) {
	    log_message(-2, "Cannot get disk information.");
	    exitCode = 1; goto fail;
	}
    }

    // ---------------------------------------------------------------------
    //   Process fullburn option for writing commands.
    // ---------------------------------------------------------------------
  
    if (options.command == SIMULATE ||
	options.command == WRITE ||
	options.command == COPY_CD) {
	if (options.fullBurn) {
	    if (options.driverId &&
		strcmp(options.driverId, "generic-mmc-raw") != 0) {
		log_message(-2, "You must use the generic-mmc-raw driver to use the "
			    "full-burn option.");
		exitCode = 1; goto fail;
	    } else {
		int mins = options.userCapacity ? options.userCapacity :
		    Msf(cdr->diskInfo()->capacity).min();
		log_message(2, "Burning entire %d mins disc.", mins);
	    }
	}
	cdr->fullBurn(options.fullBurn);
	cdr->userCapacity(options.userCapacity);
    }

    // ---------------------------------------------------------------------
    //   Setup secondary device for copy command.
    // ---------------------------------------------------------------------
  
    if (options.command == COPY_CD) {
	if (options.sourceScsiDevice != NULL && 
	    strcmp(options.scsiDevice, options.sourceScsiDevice) != 0) {
	    delSrcDevice = 1;
	    srcCdr = setupDevice(READ_CD, options.sourceScsiDevice,
				 options.sourceDriverId,
				 1, 1, 0, options.readingSpeed, false, false);

	    if (srcCdr == NULL) {
		log_message(-2, "Cannot setup source device %s.",
			    options.sourceScsiDevice);
		exitCode = 1; goto fail;
	    }

	    srcCdrScsi = srcCdr->scsiIf();

	    if ((srcDi = srcCdr->diskInfo()) == NULL) {
		log_message(-2,
			    "Cannot get disk information from source device.");
		exitCode = 1; goto fail;
	    }
	} else {
	    srcCdr = cdr;
	    srcDi = di;
	}
    }

    if (options.remoteMode)
	options.pause = false;

    // ---------------------------------------------------------------------
    //   Main command dispatch.
    // ---------------------------------------------------------------------

    switch (options.command) {
    case READ_CDDB:
	if ((exitCode = readCddb(&options, toc)) == 0) {
	    log_message(1, "Writing CD-TEXT populated toc-file \"%s\".",
			options.tocFile);
	    if (toc->write(options.tocFile) != 0)
		exitCode = 2;
	}
	break;

    case SCAN_BUS:
	scanBus();
	break;

    case DRIVE_INFO:
	showDriveInfo(cdr->driveInfo(true));
	break;

    case SHOW_TOC:
	showToc(toc);
	if (toc->check() > 1) {
	    log_message(-2, "Toc file \"%s\" is inconsistent.",
			options.tocFile);
	}
	break;

    case TOC_INFO:
	showTocInfo(toc, options.tocFile);
	if (toc->check() > 1) {
	    log_message(-2, "Toc file \"%s\" is inconsistent.",
			options.tocFile);
	}
	break;

    case TOC_SIZE:
	showTocSize(toc, options.tocFile);
	if (toc->check() > 1) {
	    log_message(-2, "Toc file \"%s\" is inconsistent.",
			options.tocFile);
	}
	break;

    case SHOW_DATA:
	showData(toc, options.swap);
	break;

    case READ_TEST:
	log_message(1, "Starting read test...");
	log_message(2, "Process can be aborted with QUIT signal "
		    "(usually CTRL-\\).");
	if (writeDiskAtOnce(toc, NULL, options.fifoBuffers,
			    options.swap, 1,
			    options.writingSpeed) != 0) {
	    log_message(-2, "Read test failed.");
	    exitCode = 1; goto fail;
	}
	break;

    case DISK_INFO:
	showDiskInfo(di);
	break;

    case DISCID:
	if (di->valid.empty && di->empty) {
	    log_message(-2, "Inserted disk is empty.");
	    exitCode = 1; goto fail;
	}
	cdr->subChanReadMode(options.readSubchanMode);
	cdr->rawDataReading(options.readRaw);
	cdr->mode2Mixed(options.mode2Mixed);
	cdr->fastTocReading(true);
	cdr->taoSource(options.taoSource);
	if (options.taoSourceAdjust >= 0)
	    cdr->taoSourceAdjust(options.taoSourceAdjust);

	cdr->force(options.force);

	if ((toc = cdr->readDiskToc(options.session,
				    (options.dataFilename == NULL) ?
				    "data.wav" : options.dataFilename))
	    == NULL) {
	    cdr->rezeroUnit(0);
	    exitCode = 1; goto fail;
	} else {
	    cdr->rezeroUnit(0);

	    if (options.printQuery)
		printCddbQuery(toc);
	    else
		readCddb(&options, toc, true);
	}
	break;
   
    case MSINFO:
	switch (showMultiSessionInfo(di)) {
	case 0:
	    log_message(2, "msinfo: Session is appendable");
	    exitCode = 0;
	    break;
      
	case 1: // CD-R is not empty and not appendable
	    log_message(2, "msinfo: CD-R is not empty and not appendable");
	    exitCode = 2;
	    break;
      
	case 2: // cannot determine state
	    log_message(2, "msinfo: cannot determine state");
	    exitCode = 3;
	    break;

	default: // everthing else is an error
	    log_message(2, "msinfo: command error");
	    exitCode = 1;
	    break;
	}
	break;


    case READ_TOC:
	if (di->valid.empty && di->empty) {
	    log_message(-2, "Inserted disk is empty.");
	    exitCode = 1; goto fail;
	}
	log_message(1, "Reading toc data...");

	if (access(options.tocFile, R_OK) == 0) {
	    log_message(-2, "File \"%s\" exists, will not overwrite.",
			options.tocFile);
	    exitCode = 1; goto fail;
	}

	cdr->subChanReadMode(options.readSubchanMode);
	cdr->rawDataReading(options.readRaw);
	cdr->mode2Mixed(options.mode2Mixed);
	cdr->fastTocReading(options.fastToc);
	cdr->taoSource(options.taoSource);
	if (options.taoSourceAdjust >= 0)
	    cdr->taoSourceAdjust(options.taoSourceAdjust);

	cdr->force(options.force);

	if ((toc =
	     cdr->readDiskToc(options.session,
			      (options.dataFilename == NULL) ?
			      "data.wav" : options.dataFilename)) == NULL) {
	    cdr->rezeroUnit(0);
	    exitCode = 1; goto fail;
	} else {
	    cdr->rezeroUnit(0);

	    if (options.withCddb) {
		if (readCddb(&options, toc) == 0) {
		    log_message(2, "CD-TEXT data was added to toc-file.");
		}
	    }

	    {
		std::ofstream out(options.tocFile);
		if (!out) {
		    log_message(-2, "Cannot open \"%s\" for writing: %s",
				options.tocFile,
				strerror(errno));
		    exitCode = 1; goto fail;
		}
		toc->print(out);
	    }

	    log_message(1, "Reading of toc data finished successfully.");
	}
	break;
    
    case READ_CD:
	if (di->valid.empty && di->empty) {
	    log_message(-2, "Inserted disk is empty.");
	    exitCode = 1; goto fail;
	}
	log_message(1, "Reading toc and track data...");

	if (access(options.tocFile, R_OK) == 0) {
	    log_message(-2, "File \"%s\" exists, will not overwrite.",
			options.tocFile);
	    exitCode = 1; goto fail;
	}

	cdr->subChanReadMode(options.readSubchanMode);
	cdr->rawDataReading(options.readRaw);
	cdr->mode2Mixed(options.mode2Mixed);
	cdr->taoSource(options.taoSource);
	if (options.taoSourceAdjust >= 0)
	    cdr->taoSourceAdjust(options.taoSourceAdjust);

	cdr->paranoiaMode(options.paranoiaMode);
	cdr->fastTocReading(options.fastToc);
	cdr->remote(options.remoteMode, options.remoteFd);
	cdr->force(options.force);

	toc = cdr->readDisk(options.session,
			    (options.dataFilename == NULL) ? "data.bin" :
			    options.dataFilename);
      
	if (toc == NULL) {
	    cdr->rezeroUnit(0);
	    exitCode = 1; goto fail;
	}
	cdr->rezeroUnit(0);

	if (options.withCddb) {
	    if (readCddb(&options, toc) == 0) {
		log_message(2, "CD-TEXT data was added to toc-file.");
	    }
	}

	{
	    std::ofstream out(options.tocFile);
	    if (!out) {
		log_message(-2, "Cannot open \"%s\" for writing: %s",
			    options.tocFile, strerror(errno));
		exitCode = 1; goto fail;
	    }
	    toc->print(out);
	}

	log_message(1, "Reading of toc and track data finished successfully.");
	break;

    case WRITE:
	if (!options.writeSimulate)
	    cdr->simulate(false);
	// fall through
    
    case SIMULATE:
	if (di->valid.empty && !di->empty && 
	    (!di->valid.append || !di->append)) {
	    log_message(-2, "Inserted disk is not empty and not appendable.");
	    exitCode = 1; goto fail;
	}

	if (toc->length().lba() > di->capacity) {
	    log_message((options.overburn ? -1 : -2),
			"Length of toc (%s, %ld blocks) exceeds capacity ",
			toc->length().str(), toc->length().lba());
	    log_message(0, "of CD-R (%s, %ld blocks).",
			Msf(di->capacity).str(),
			di->capacity);

	    if (options.overburn) {
		log_message(-1, "Ignored because of option '--overburn'.");
		log_message(-1, "Some drives may fail to record this toc.");
	    } else {
		log_message(-2, "Please use option '--overburn' to start"
			    "recording anyway.");
		exitCode = 1; goto fail;
	    }
	}

	if (options.multiSession) {
	    if (cdr->multiSession(1) != 0) {
		log_message(-2, "This driver does not support "
			    "multi session discs.");
		exitCode = 1; goto fail;
	    }
	}

	if (options.writingSpeed >= 0) {
	    if (cdr->speed(options.writingSpeed) != 0) {
		log_message(-2, "Writing speed %d not supported by device.",
			    options.writingSpeed);
		exitCode = 1; goto fail;
	    }
	}

	cdr->bufferUnderRunProtection(options.bufferUnderrunProtection);
	cdr->writeSpeedControl(options.writeSpeedControl);

	cdr->force(options.force);
	cdr->remote(options.remoteMode, options.remoteFd);

	switch (cdr->checkToc(toc)) {
	case 0: // OK
	    break;
	case 1: // warning
	    if (!options.force && !options.remoteMode) {
		log_message(-2, "Toc-file \"%s\" may create undefined "
			    "results.", options.tocFile);
		log_message(-2, "Use option --force to use it anyway.");
		exitCode = 1; goto fail;
	    }
	    break;
	default: // error
	    log_message(-2, "Toc-file \"%s\" is not suitable for this drive.",
			options.tocFile);
	    exitCode = 1; goto fail;
	    break;
	}

	log_message(1, "Starting write ");
	if (cdr->simulate()) {
	    log_message(1, "simulation ");
	}
	log_message(1, "at speed %d...", cdr->speed());
	if (cdr->multiSession() != 0) {
	    log_message(1, "Using multi session mode.");
	}

	if (options.pause) {
	    log_message(1, "Pausing 10 seconds - hit CTRL-C to abort.");
	    sleep(10);
	}

	log_message(2, "Process can be aborted with QUIT signal "
		    "(usually CTRL-\\).");
	if (cdr->preventMediumRemoval(1) != 0) {
	    exitCode = 1; goto fail;
	}

	if (writeDiskAtOnce(toc, cdr, options.fifoBuffers,
			    options.swap, 0, 0) != 0) {
	    if (cdr->simulate()) {
		log_message(-2, "Simulation failed.");
	    } else {
		log_message(-2, "Writing failed.");
	    }
	    cdr->preventMediumRemoval(0);
	    cdr->rezeroUnit(0);
	    exitCode = 1; goto fail;
	}

	if (cdr->simulate()) {
	    log_message(1, "Simulation finished successfully.");
	} else {
	    log_message(1, "Writing finished successfully.");
	}

	cdr->rezeroUnit(0);
	if (cdr->preventMediumRemoval(0) != 0) {
	    exitCode = 1; goto fail;
	}

	if (options.eject) {
	    cdr->loadUnload(1);
	}
	break;

    case COPY_CD:
	if (cdr != srcCdr) {
	    if (di->valid.empty && !di->empty && 
		(!di->valid.append || !di->append)) {
		log_message(-2, "Medium in recorder device is not empty"
			    "and not appendable.");
		exitCode = 1; goto fail;
	    }
	}

	if (srcDi->valid.empty && srcDi->empty) {
	    log_message(-2, "Medium in source device is empty.");
	    exitCode = 1; goto fail;
	}
    
	cdr->simulate(options.writeSimulate);
	cdr->force(options.force);
	cdr->remote(options.remoteMode, options.remoteFd);

	cdr->bufferUnderRunProtection(options.bufferUnderrunProtection);
	cdr->writeSpeedControl(options.writeSpeedControl);
    
	if (options.multiSession) {
	    if (cdr->multiSession(1) != 0) {
		log_message(-2, "This driver does not support multi"
			    "session discs.");
		exitCode = 1; goto fail;
	    }
	}

	if (options.writingSpeed >= 0) {
	    if (cdr->speed(options.writingSpeed) != 0) {
		log_message(-2, "Writing speed %d not supported by device.",
			    options.writingSpeed);
		exitCode = 1; goto fail;
	    }
	}

	srcCdr->paranoiaMode(options.paranoiaMode);
	srcCdr->subChanReadMode(options.readSubchanMode);
	srcCdr->fastTocReading(options.fastToc);
	srcCdr->force(options.force);
    
	if (options.onTheFly)
	    log_message(1, "Starting on-the-fly CD copy ");
	else
	    log_message(1, "Starting CD copy ");
	if (cdr->simulate()) {
	    log_message(1, "simulation ");
	}
	log_message(1, "at speed %d...", cdr->speed());
	if (cdr->multiSession() != 0) {
	    log_message(1, "Using multi session mode.");
	}

	if (options.onTheFly) {
	    if (srcCdr == cdr) {
		log_message(-2, "Two different device are required "
			    "for on-the-fly copying.");
		log_message(-2, "Please use option '--source-device x,y,z'.");
		exitCode = 1; goto fail;
	    }

	    if (copyCdOnTheFly(&options, srcCdr, cdr) == 0) {
		log_message(1, "On-the-fly CD copying finished successfully.");
	    } else {
		log_message(-2, "On-the-fly CD copying failed.");
		exitCode = 1; goto fail;
	    }

	} else {
	    if (srcCdr != cdr)
		srcCdr->remote(options.remoteMode, options.remoteFd);

	    if (copyCd(&options, srcCdr, cdr) == 0) {
		log_message(1, "CD copying finished successfully.");
	    } else {
		log_message(-2, "CD copying failed.");
		exitCode = 1; goto fail;
	    }
	}
	break;

    case BLANK:
	if (options.writingSpeed >= 0) {
	    if (cdr->speed(options.writingSpeed) != 0) {
		log_message(-2, "Blanking speed %d not supported by device.",
			    options.writingSpeed);
		exitCode = 1; goto fail;
	    }
	}

	cdr->remote(options.remoteMode, options.remoteFd);
	cdr->simulate(options.writeSimulate);

	log_message(1, "Blanking disk...");
	if (cdr->blankDisk(options.blankingMode) != 0) {
	    log_message(-2, "Blanking failed.");
	    exitCode = 1; goto fail;
	}

	if (options.eject)
	    cdr->loadUnload(1);
	break;

    case UNLOCK:
	log_message(1, "Trying to unlock drive...");

	cdr->abortDao();

	if (cdr->preventMediumRemoval(0) != 0) {
	    exitCode = 1; goto fail;
	}

	if (options.eject)
	    cdr->loadUnload(1);
	break;

    case UNKNOWN:
	assert(0);
	break;
    case SHOW_VERSION:
    case LAST_CMD:
	/* To avoid warning */
	break;
    }

 fail:
    delete cdr;
    if (delSrcDevice)
	delete srcCdr;
    delete cdrScsi;
    if (delSrcDevice)
	delete srcCdrScsi;

    delete toc;

#ifdef __CYGWIN__
    if (isNT) {
	DWORD bytes;
	if (fh)	{
	    if (!DeviceIoControl (fh, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL))
		log_message(-2, "Couldn't unlock device %s!", devstr);
	    else
		log_message(2, "Device %s unlocked.", devstr);
	    CloseHandle (fh);
	}
    }
#endif
    exit(exitCode);
}