/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */

/* 
   Copyright (c) 2006 - 2013 Thomas Schmitt <scdbackup@gmx.net>
   Provided under GPL version 2 or later
        and under FreeBSD license revised, i.e. without advertising clause.
*/

#include "../config.h"

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>

#include <err.h> /* XXX */

/* ts A70909 */
#include <sys/statvfs.h>

/* ts B00121 */
#include <sys/disk.h> /* DIOCGMEDIASIZE */


/* ts B00326 : For use of CAM_PASS_ERR_RECOVER with ahci */
#define Libburn_for_freebsd_ahcI yes

/* ts B00327 : for debugging of cam_send_cdb() failures
 # define Libburn_ahci_verbouS yes
*/

/* ts B00327 : Apply CAM_PASS_ERR_RECOVER to drives even if not ahci
 # define libburn_ahci_style_for_alL yes
*/


#include "transport.h"
#include "drive.h"
#include "sg.h"
#include "spc.h"
#include "mmc.h"
#include "sbc.h"
#include "debug.h"
#include "toc.h"
#include "util.h"
#include "init.h"

#include "libdax_msgs.h"
extern struct libdax_msgs *libdax_messenger;

struct burn_drive_enumeration_state {
	int fd;
	union ccb ccb;
	unsigned int i;
	int skip_device;
};

static void enumerate_common(char *fname, int bus_no, int host_no,
			     int channel_no, int target_no, int lun_no);

/* ts A51221 */
int burn_drive_is_banned(char *device_address);


/* ts A60821
   debug: for tracing calls which might use open drive fds
          or for catching SCSI usage of emulated drives. */
int mmc_function_spy(struct burn_drive *d, char * text);


/* ts B00113
   Whether to log SCSI commands:
   bit0= log in /tmp/libburn_sg_command_log
   bit1= log to stderr
   bit2= flush every line
*/
extern int burn_sg_log_scsi;

/* ts B00114 */
/* Storage object is in libburn/init.c
   whether to strive for exclusive access to the drive
*/
extern int burn_sg_open_o_excl;


/* ts A91227 */
/** Returns the id string  of the SCSI transport adapter and eventually
    needed operating system facilities.
    This call is usable even if sg_initialize() was not called yet. In that
    case a preliminary constant message might be issued if detailed info is
    not available yet.
    @param msg   returns id string
    @param flag  unused yet, submit 0
    @return      1 = success, <=0 = failure
*/
int sg_id_string(char msg[1024], int flag)
{
	strcpy(msg, "internal FreeBSD CAM adapter sg-freebsd");
	return 1;
}


/* ts A91227 */
/** Performs global initialization of the SCSI transport adapter and eventually
    needed operating system facilities. Checks for compatibility supporting
    software components.
    @param msg   returns ids and/or error messages of eventual helpers
    @param flag  unused yet, submit 0
    @return      1 = success, <=0 = failure
*/
int sg_initialize(char msg[1024], int flag)
{
	return sg_id_string(msg, 0);
}


/* ts A91227 */
/** Performs global finalization of the SCSI transport adapter and eventually
    needed operating system facilities. Releases globally acquired resources.
    @param flag  unused yet, submit 0
    @return      1 = success, <=0 = failure
*/  
int sg_shutdown(int flag)
{
	return 1;
}


/** Finalizes BURN_OS_TRANSPORT_DRIVE_ELEMENTS, the components of
    struct burn_drive which are defined in os-*.h.
    The eventual initialization of those components was made underneath
    scsi_enumerate_drives().
    This will be called when a burn_drive gets disposed.
    @param d     the drive to be finalized
    @param flag  unused yet, submit 0
    @return      1 = success, <=0 = failure
*/
int sg_dispose_drive(struct burn_drive *d, int flag)
{
        return 1;
}


/* ts A61021 : Moved most code from scsi_enumerate_drives under
               sg_give_next_adr() */
/* Some helper functions for scsi_give_next_adr() */

static int sg_init_enumerator(burn_drive_enumerator_t *idx_)
{
	struct burn_drive_enumeration_state *idx;
	int bufsize;

	idx = calloc(1, sizeof(*idx));
	if (idx == NULL) {
		warnx("cannot allocate memory for enumerator");
		return -1;
	}
	idx->skip_device = 0;

	if ((idx->fd = open(XPT_DEVICE, O_RDWR)) == -1) {
		warn("could not open %s", XPT_DEVICE);
		free(idx);
		idx = NULL;
		return -1;
	}

	memset(&(idx->ccb), 0, sizeof(union ccb));

	idx->ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
	idx->ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
	idx->ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;

	idx->ccb.ccb_h.func_code = XPT_DEV_MATCH;
	bufsize = sizeof(struct dev_match_result) * 100;
	idx->ccb.cdm.match_buf_len = bufsize;
	idx->ccb.cdm.matches = (struct dev_match_result *) calloc(1, bufsize);
	if (idx->ccb.cdm.matches == NULL) {
		warnx("cannot allocate memory for matches");
		close(idx->fd);
		free(idx);
		return -1;
	}
	idx->ccb.cdm.num_matches = 0;
	idx->i = idx->ccb.cdm.num_matches; /* to trigger buffer load */

	/*
	 * We fetch all nodes, since we display most of them in the default
	 * case, and all in the verbose case.
	 */
	idx->ccb.cdm.num_patterns = 0;
	idx->ccb.cdm.pattern_buf_len = 0;

	*idx_ = idx;

	return 1;
}

static void sg_destroy_enumerator(burn_drive_enumerator_t *idx_)
{
	struct burn_drive_enumeration_state *idx = *idx_;

	if(idx->fd != -1)
		close(idx->fd);

	free(idx->ccb.cdm.matches);
	free(idx);

	*idx_ = NULL;
}

static int sg_next_enumeration_buffer(burn_drive_enumerator_t *idx_)
{
	struct burn_drive_enumeration_state *idx = *idx_;

	/*
	 * We do the ioctl multiple times if necessary, in case there are
	 * more than 100 nodes in the EDT.
	 */
	if (ioctl(idx->fd, CAMIOCOMMAND, &(idx->ccb)) == -1) {
		warn("error sending CAMIOCOMMAND ioctl");
		return -1;
	}

	if ((idx->ccb.ccb_h.status != CAM_REQ_CMP)
	    || ((idx->ccb.cdm.status != CAM_DEV_MATCH_LAST)
		&& (idx->ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
		warnx("got CAM error %#x, CDM error %d\n",
		      idx->ccb.ccb_h.status, idx->ccb.cdm.status);
		return -1;
	}
	return 1;
}


/** Returns the next index object state and the next enumerated drive address.
    @param idx An opaque handle. Make no own theories about it.
    @param adr Takes the reply
    @param adr_size Gives maximum size of reply including final 0
    @param initialize  1 = start new,
                       0 = continue, use no other values for now
                      -1 = finish
    @return 1 = reply is a valid address , 0 = no further address available
           -1 = severe error (e.g. adr_size too small)
*/
int sg_give_next_adr(burn_drive_enumerator_t *idx_,
		     char adr[], int adr_size, int initialize)
{
	struct burn_drive_enumeration_state *idx;
	int ret;

	if (initialize == 1) {
		ret = sg_init_enumerator(idx_);
		if (ret<=0)
			return ret;
	} else if (initialize == -1) {
		sg_destroy_enumerator(idx_);
		return 0;
	}

	idx = *idx_;

	do {
		if (idx->i >= idx->ccb.cdm.num_matches) {
			ret = sg_next_enumeration_buffer(idx_);
			if (ret<=0)
				return -1;
			idx->i = 0;
		} else
			(idx->i)++;

		while (idx->i < idx->ccb.cdm.num_matches) {
			switch (idx->ccb.cdm.matches[idx->i].type) {
			case DEV_MATCH_BUS:
				break;
			case DEV_MATCH_DEVICE: {
				struct device_match_result* result;

				result = &(idx->ccb.cdm.matches[idx->i].result.device_result);
				if (result->flags & DEV_RESULT_UNCONFIGURED)
					idx->skip_device = 1;
				else
					idx->skip_device = 0;
				break;
			}
			case DEV_MATCH_PERIPH: {
				struct periph_match_result* result;

				result = &(idx->ccb.cdm.matches[idx->i].result.periph_result);
/* ts B00112 : we really want only "cd" devices.

				if (idx->skip_device || 
				    strcmp(result->periph_name, "pass") == 0)
					break;
*/
				if (idx->skip_device || 
				    strcmp(result->periph_name, "cd") != 0)
					break;
				ret = snprintf(adr, adr_size, "/dev/%s%d",
					 result->periph_name, result->unit_number);
				if(ret >= adr_size)
					return -1;

				/* Found next enumerable address */
				return 1;

			}
			default:
				/* fprintf(stderr, "unknown match type\n"); */
				break;
			}
			(idx->i)++;
		}
	} while ((idx->ccb.ccb_h.status == CAM_REQ_CMP)
		&& (idx->ccb.cdm.status == CAM_DEV_MATCH_MORE));

	return 0;
}


int sg_is_enumerable_adr(char* adr)
{
	burn_drive_enumerator_t idx;
	int ret;
	char buf[64];

	ret = sg_init_enumerator(&idx);
	if (ret <= 0)
		return 0;
	while(1) {
		ret = sg_give_next_adr(&idx, buf, sizeof(buf), 0);
		if (ret <= 0)
			break;
		if (strcmp(adr, buf) == 0) {
			sg_destroy_enumerator(&idx);
			return 1;
		}
	}
	sg_destroy_enumerator(&idx);
	return (0);
}


/** Try to obtain SCSI address parameters.
    @return  1 is success , 0 is failure
*/
int sg_obtain_scsi_adr(char *path, int *bus_no, int *host_no, int *channel_no,
                       int *target_no, int *lun_no)
{
	burn_drive_enumerator_t idx;
	int ret;
	char buf[64];
	struct periph_match_result* result;

	ret = sg_init_enumerator(&idx);
	if (ret <= 0)
		return 0;
	while(1) {
		ret = sg_give_next_adr(&idx, buf, sizeof(buf), 0);
		if (ret <= 0)
			break;
		if (strcmp(path, buf) == 0) {
			result = &(idx->ccb.cdm.matches[idx->i].result.periph_result);
			*bus_no = result->path_id;
			*host_no = result->path_id;
			*channel_no = 0;
			*target_no = result->target_id;
			*lun_no = result->target_lun;
			sg_destroy_enumerator(&idx);
			return 1;
		}
	}
	sg_destroy_enumerator(&idx);
	return (0);
}


int sg_close_drive(struct burn_drive * d)
{
	if (d->cam != NULL) {
		cam_close_device(d->cam);
		d->cam = NULL;
	}
	if (d->lock_fd > 0) {
		close(d->lock_fd);
		d->lock_fd = -1;
	}
	return 0;
}

int sg_drive_is_open(struct burn_drive * d)
{
	return (d->cam != NULL);
}

int scsi_enumerate_drives(void)
{
	burn_drive_enumerator_t idx;
	int ret;
	char buf[64];
	struct periph_match_result* result;

	ret = sg_init_enumerator(&idx);
	if (ret <= 0)
		return 0;
	while(1) {
		ret = sg_give_next_adr(&idx, buf, sizeof(buf), 0);
		if (ret <= 0)
			break;
		if (burn_drive_is_banned(buf))
			continue; 
		result = &idx->ccb.cdm.matches[idx->i].result.periph_result;
		enumerate_common(buf, result->path_id, result->path_id,
				0, result->target_id, 
				result->target_lun);
	}
	sg_destroy_enumerator(&idx);

	return 1;
}


#ifdef Scsi_freebsd_make_own_enumeratE

/* ts A61021: The old version which mixes SCSI and operating system adapter
*/
static void enumerate_common(char *fname, int bus_no, int host_no,
			     int channel_no, int target_no, int lun_no)
{
	struct burn_drive *t;
	struct burn_drive out;

	/* Initialize pointers to managed memory */
	out.devname = NULL;
	out.idata = NULL;
	out.mdata = NULL;

	/* ts A60923 */
	out.bus_no = bus_no;
	out.host = host_no;
	out.id = target_no;
	out.channel = channel_no;
	out.lun = lun_no;

	out.devname = strdup(fname);
	if (out.devname == NULL)
		goto could_not_allocate;

	out.cam = NULL;
	out.lock_fd = -1;
	out.is_ahci = 0;

	out.start_lba= -2000000000;
	out.end_lba= -2000000000;
	out.read_atip = mmc_read_atip;

	out.grab = sg_grab;
	out.release = sg_release;
	out.drive_is_open= sg_drive_is_open;
	out.issue_command = sg_issue_command;
	out.getcaps = spc_getcaps;
	out.released = 1;
	out.status = BURN_DISC_UNREADY;

	out.eject = sbc_eject;
	out.load = sbc_load;
	out.lock = spc_prevent;
	out.unlock = spc_allow;
	out.read_disc_info = spc_sense_write_params;
	out.get_erase_progress = spc_get_erase_progress;
	out.test_unit_ready = spc_test_unit_ready;
	out.probe_write_modes = spc_probe_write_modes;
	out.read_toc = mmc_read_toc;
	out.write = mmc_write;
	out.erase = mmc_erase;
	out.read_cd = mmc_read_cd;
	out.perform_opc = mmc_perform_opc;
	out.set_speed = mmc_set_speed;
	out.send_parameters = spc_select_error_params;
	out.send_write_parameters = spc_select_write_params;
	out.send_cue_sheet = mmc_send_cue_sheet;
	out.sync_cache = mmc_sync_cache;
	out.get_nwa = mmc_get_nwa;
	out.close_disc = mmc_close_disc;
	out.close_session = mmc_close_session;
	out.close_track_session = mmc_close;
	out.read_buffer_capacity = mmc_read_buffer_capacity;
	out.idata = calloc(1, sizeof(struct burn_scsi_inquiry_data));
	out.idata->valid = 0;
	out.mdata = calloc(1, sizeof(struct scsi_mode_data));
	if (out.idata == NULL || out.mdata == NULL) {
could_not_allocate:;
		libdax_msgs_submit(libdax_messenger, -1, 0x00020108,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Could not allocate new drive object", 0, 0);
		if (out.devname != NULL)
			free(out.devname);
		out.devname = NULL;
		if (out.idata != NULL)
			free(out.idata);
		out.idata = NULL;
		if (out.mdata != NULL)
			free(out.mdata);
		out.mdata = NULL;
		return;
	}
	out.mdata->p2a_valid = 0;
	memset(&out.params, 0, sizeof(struct params));
	t = burn_drive_register(&out);

/* ts A60821
   <<< debug: for tracing calls which might use open drive fds */
	mmc_function_spy(NULL, "enumerate_common : -------- doing grab");

/* try to get the drive info */
	if (t->grab(t)) {
		t->getcaps(t);
		t->unlock(t);
		t->released = 1;
	}

/* ts A60821
   <<< debug: for tracing calls which might use open drive fds */
	mmc_function_spy(NULL, "enumerate_common : ----- would release ");

}

#else /* Scsi_freebsd_make_own_enumeratE */

/* The new, more concise version of enumerate_common */
static void enumerate_common(char *fname, int bus_no, int host_no,
			     int channel_no, int target_no, int lun_no)
{
	int ret;
	struct burn_drive out;

	/* General libburn drive setup */
	burn_setup_drive(&out, fname);

	/* This transport adapter uses SCSI-family commands and models
	   (seems the adapter would know better than its boss, if ever) */
	ret = burn_scsi_setup_drive(&out, bus_no, host_no, channel_no,
                                 target_no, lun_no, 0);
        if (ret<=0)
                return;

	/* Operating system adapter is CAM */
	/* Adapter specific handles and data */
	out.cam = NULL;
	out.lock_fd = -1;
	out.is_ahci = 0;

	/* Adapter specific functions */
	out.grab = sg_grab;
	out.release = sg_release;
	out.drive_is_open = sg_drive_is_open;
	out.issue_command = sg_issue_command;

	/* Finally register drive and inquire drive information */
	burn_drive_finish_enum(&out);
}

#endif /* ! Scsi_freebsd_make_own_enumeratE */


/* Lock the inode associated to dev_fd and the inode associated to devname.
   Return OS errno, number of pass device of dev_fd, locked fd to devname,
   error message.
   A return value of > 0 means success, <= 0 means failure.
*/
static int freebsd_dev_lock(int dev_fd, char *devname,
	 int *os_errno, int *pass_dev_no, int *lock_fd, char msg[4096],
	 int flag)
{
	int lock_denied = 0, fd_stbuf_valid, name_stbuf_valid, i, pass_l = 100;
	int max_retry = 3, tries = 0;
	struct stat fd_stbuf, name_stbuf;
	char pass_name[16], *lock_name;

	*os_errno = 0;
	*pass_dev_no = -1;
	*lock_fd = -1;
	msg[0] = 0;

	fd_stbuf_valid = !fstat(dev_fd, &fd_stbuf);

	/* Try to find name of pass device by inode number */
	lock_name = (char *) "effective device";
	if(fd_stbuf_valid) {
		for (i = 0; i < pass_l; i++) {
			sprintf(pass_name, "/dev/pass%d", i);
			if (stat(pass_name, &name_stbuf) != -1)
				if(fd_stbuf.st_ino == name_stbuf.st_ino &&
			   	fd_stbuf.st_dev == name_stbuf.st_dev)	
		break;
		}
		if (i < pass_l) {
			lock_name = pass_name;
			*pass_dev_no = i;
		}
	}

	name_stbuf_valid = !stat(devname, &name_stbuf);
	for (tries= 0; tries <= max_retry; tries++) {
		lock_denied = flock(dev_fd, LOCK_EX | LOCK_NB);
		*os_errno = errno;
		if (lock_denied) {
			if (errno == EAGAIN && tries < max_retry) {
				/* <<< debugging
				fprintf(stderr,
				"\nlibcdio_DEBUG: EAGAIN pass, tries= %d\n",
					tries);
				*/
				usleep(2000000);
	continue;
			}
			sprintf(msg,
			    "Device busy. flock(LOCK_EX) failed on %s of %s",
			    strlen(lock_name) > 2000 || *pass_dev_no < 0 ?
						 "pass device" : lock_name,
			    strlen(devname) > 2000 ? "drive" : devname);
			return 0;
		}
	break;
	}

	/*
	fprintf(stderr, "libburn_DEBUG: flock obtained on %s of %s\n",
			lock_name, devname);
	*/

	/* Eventually lock the official device node too */
	if (fd_stbuf_valid && name_stbuf_valid &&
		(fd_stbuf.st_ino != name_stbuf.st_ino ||
		 fd_stbuf.st_dev != name_stbuf.st_dev)) {

		*lock_fd = open(devname, O_RDONLY);
		if (*lock_fd == 0) {
			close(*lock_fd);
			*lock_fd = -1;
		} if (*lock_fd > 0) {
			for (tries = 0; tries <= max_retry; tries++) {
				lock_denied = 
					flock(*lock_fd, LOCK_EX | LOCK_NB);
				if (lock_denied) {
					if (errno == EAGAIN &&
							 tries < max_retry) {
						/* <<< debugging
						fprintf(stderr,
				"\nlibcdio_DEBUG: EAGAIN dev, tries= %d\n",
							tries);
						*/

						usleep(2000000);
			continue;
					}
					close(*lock_fd);
					*lock_fd = -1;
					sprintf(msg,
				 "Device busy. flock(LOCK_EX) failed on %s",
				 strlen(devname) > 4000 ? "drive" : devname);
					return 0;
				}
			break;
			}
		}

/*
		fprintf(stderr, "libburn_DEBUG: flock obtained on %s\n",
				devname);
*/

	}
	return 1;
}


static int sg_lock(struct burn_drive *d, int flag)
{
	int ret, os_errno, pass_dev_no = -1, flock_fd = -1;
	char *msg = NULL;

	BURN_ALLOC_MEM(msg, char, 4096);
	ret = freebsd_dev_lock(d->cam->fd, d->devname,
				&os_errno, &pass_dev_no, &flock_fd, msg, 0);
	if (ret <= 0) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020008,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			msg, os_errno, 0);
		sg_close_drive(d);
		{ret = 0; goto ex;}
	}
	if (d->lock_fd > 0)
		close(d->lock_fd);
	d->lock_fd = flock_fd;
	ret = 1;
ex:;
	BURN_FREE_MEM(msg);
	return ret;
}


int sg_grab(struct burn_drive *d)
{
	struct cam_device *cam;
	char path_string[80];

	if (mmc_function_spy(d, "sg_grab") <= 0)
		return 0;

	if (burn_drive_is_open(d)) {
		d->released = 0;
		return 1;
	}

	cam = cam_open_device(d->devname, O_RDWR);
	if (cam == NULL) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020003,
				LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				"Could not grab drive", errno, 0);
		return 0;
	}
	d->cam = cam;
	if (burn_sg_open_o_excl & 63)
		if (sg_lock(d, 0) <= 0)
			return 0;
	fcntl(cam->fd, F_SETOWN, getpid());

	cam_path_string(d->cam, path_string, sizeof(path_string));

#ifdef Libburn_ahci_verbouS
	fprintf(stderr, "libburn_EXPERIMENTAL: CAM path = %s\n", path_string);
#endif

	if (strstr(path_string, ":ahcich") != NULL)
		d->is_ahci = 1;
	else
		d->is_ahci = -1;

	d->released = 0;
	return 1;
}


/*
	non zero return means you still have the drive and it's not
	in a state to be released? (is that even possible?)
*/

int sg_release(struct burn_drive *d)
{
	if (mmc_function_spy(d, "sg_release") <= 0)
		return 0;

	if (d->cam == NULL)
		return 0;

	mmc_function_spy(NULL, "sg_release ----------- closing.");

	sg_close_drive(d);
	d->released = 1;
	return 0;
}

int sg_issue_command(struct burn_drive *d, struct command *c)
{
	int done = 0, err, sense_len = 0, ret, ignore_error, i;
	int cam_pass_err_recover = 0, key, asc, ascq, timeout_ms;
	union ccb *ccb;
	static FILE *fp = NULL;
	time_t start_time;

	mmc_function_spy(NULL, "sg_issue_command");

	c->error = 0;
	memset(c->sense, 0, sizeof(c->sense));

	if (d->cam == NULL)
		return 0;
	if (burn_sg_log_scsi & 1) {
		if (fp == NULL) {
			fp= fopen("/tmp/libburn_sg_command_log", "a");
			fprintf(fp,
			    "\n-----------------------------------------\n");
		}
	}
	if (burn_sg_log_scsi & 3)
		scsi_log_cmd(c,fp,0);

	c->error = 0;
	if (c->timeout > 0)
		timeout_ms = c->timeout;
	else
		timeout_ms = 200000;

	ccb = cam_getccb(d->cam);
	cam_fill_csio(&ccb->csio,
				  1,                              /* retries */
				  NULL,                           /* cbfncp */
				  CAM_DEV_QFRZDIS,                /* flags */
				  MSG_SIMPLE_Q_TAG,               /* tag_action */
				  NULL,                           /* data_ptr */
				  0,                              /* dxfer_len */
				  sizeof (ccb->csio.sense_data),  /* sense_len */
				  0,                              /* cdb_len */
				  timeout_ms);                    /* timeout */
	switch (c->dir) {
	case TO_DRIVE:
		ccb->csio.ccb_h.flags |= CAM_DIR_OUT;
		break;
	case FROM_DRIVE:
		ccb->csio.ccb_h.flags |= CAM_DIR_IN;
		break;
	case NO_TRANSFER:
		ccb->csio.ccb_h.flags |= CAM_DIR_NONE;
		break;
	}

#ifdef Libburn_for_freebsd_ahcI
	/* ts B00325 : Advise by Alexander Motin */
        /* Runs well on 8-STABLE (23 Mar 2003)
	   But on 8-RELEASE cam_send_ccb() returns non-zero with errno 6
           on eject. Long lasting TEST UNIT READY cycles break with
           errno 16.
        */
#ifdef Libburn_ahci_style_for_alL
	{
#else
	if (d->is_ahci > 0) {
#endif
		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
		cam_pass_err_recover = 1;
	}
#endif /* Libburn_for_freebsd_ahcI */

	ccb->csio.cdb_len = c->oplen;
	memcpy(&ccb->csio.cdb_io.cdb_bytes, &c->opcode, c->oplen);
	
	if (c->page) {
		ccb->csio.data_ptr  = c->page->data;
		if (c->dir == FROM_DRIVE) {

			/* ts A90430 : Ticket 148 , by jwehle :
			   "On ... FreeBSD 6.4 which has a usb memory reader in
			    addition to a ATAPI DVD burner sg_issue_command
			    will hang while the SCSI bus is being scanned"
			*/
			if (c->dxfer_len >= 0)
				ccb->csio.dxfer_len = c->dxfer_len;
			else
				ccb->csio.dxfer_len = BUFFER_SIZE;

/* touch page so we can use valgrind */
			memset(c->page->data, 0, BUFFER_SIZE);
		} else {
			ccb->csio.dxfer_len = c->page->bytes;
		}
	} else {
		ccb->csio.data_ptr  = NULL;
		ccb->csio.dxfer_len = 0;
	}

	/* ts B90523 : Record effective transfer length request for debugging*/
	c->dxfer_len = ccb->csio.dxfer_len;

	start_time = time(NULL);
	for (i = 0; !done; i++) {

		memset(&ccb->csio.sense_data, 0, sizeof(ccb->csio.sense_data));
		memset(c->sense, 0, sizeof(c->sense));
		c->start_time = burn_get_time(0);

		err = cam_send_ccb(d->cam, ccb);

		c->end_time = burn_get_time(0);
		ignore_error = sense_len = 0;
		/* ts B00325 : CAM_AUTOSNS_VALID advised by Alexander Motin */
		if (ccb->ccb_h.status & CAM_AUTOSNS_VALID) {
			/* ts B00110 */
			/* Better curb sense_len */
			sense_len = ccb->csio.sense_len;
			if (sense_len > (int) sizeof(c->sense))
				sense_len = sizeof(c->sense);
			memcpy(c->sense, &ccb->csio.sense_data, sense_len);
			spc_decode_sense(c->sense, sense_len,
							&key, &asc, &ascq);
			if (sense_len >= 14 && cam_pass_err_recover && key)
				ignore_error = 1;
		}

		if (err == -1 && cam_pass_err_recover && ! ignore_error) {

#ifdef Libburn_ahci_verbouS
			fprintf(stderr, "libburn_EXPERIMENTAL: errno = %d . cam_errbuf = '%s'\n", errno, cam_errbuf);
#endif

			if (errno == ENXIO && c->opcode[0] != 0) {
				/* Operations on empty or ejected tray */
				/* MEDIUM NOT PRESENT */

#ifdef Libburn_ahci_verbouS
				fprintf(stderr, "libburn_EXPERIMENTAL: Emulating [2,3A,00] MEDIUM NOT PRESENT\n");
#endif

				c->sense[0] = 0x70; /*Fixed format sense data*/
				c->sense[2] = 0x02;
				c->sense[12] = 0x3A;
				c->sense[13] = 0x00;
				sense_len = 14;
				ignore_error = 1;
			} else if (c->opcode[0] == 0 && 
					(errno == EBUSY || errno == ENXIO)) {
				/* Timeout of TEST UNIT READY loop */
				/* Inquiries while tray is being loaded */
				/*LOGICAL UNIT NOT READY,CAUSE NOT REPORTABLE*/

#ifdef Libburn_ahci_verbouS
				fprintf(stderr, "libburn_EXPERIMENTAL: Emulating [2,04,00] LOGICAL UNIT NOT READY,CAUSE NOT REPORTABLE\n");
#endif

				c->sense[0] = 0x70; /*Fixed format sense data*/
				c->sense[2] = 0x02;
				c->sense[12] = 0x04;
				c->sense[13] = 0x00;
				sense_len = 14;
				ignore_error = 1;
			} else if (errno == EINVAL) {
				/* Inappropriate MODE SENSE */
				/* INVALID FIELD IN CDB */

#ifdef Libburn_ahci_verbouS
				fprintf(stderr, "libburn_EXPERIMENTAL: Emulating [5,24,00] INVALID FIELD IN CDB\n");
#endif

				c->sense[0] = 0x70; /*Fixed format sense data*/
				c->sense[2] = 0x05;
				c->sense[12] = 0x24;
				c->sense[13] = 0x00;
				sense_len = 14;
				ignore_error = 1;
			}
		}

		if (err == -1 && !ignore_error) {
			libdax_msgs_submit(libdax_messenger,
				d->global_index, 0x0002010c,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				"Failed to transfer command to drive",
				errno, 0);
			sg_close_drive(d);
			d->released = 1;
			d->busy = BURN_DRIVE_IDLE;
			c->error = 1;
			{ret = -1; goto ex;}
		}
		/* XXX */

		if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
			if (sense_len < 14) {
				/*LOGICAL UNIT NOT READY,CAUSE NOT REPORTABLE*/

#ifdef Libburn_ahci_verbouS
				fprintf(stderr, "libburn_EXPERIMENTAL: CAM_STATUS= %d .Emulating [2,04,00] LOGICAL UNIT NOT READY,CAUSE NOT REPORTABLE\n", (ccb->ccb_h.status & CAM_STATUS_MASK));
#endif

				c->sense[0] = 0x70; /*Fixed format sense data*/
				c->sense[2] = 0x02;
				c->sense[12] = 0x04;
				c->sense[13] = 0x00;
				done = 1;
			}
		}
		done = scsi_eval_cmd_outcome(d, c, fp, c->sense,
						sense_len, start_time,
						timeout_ms, i, !!ignore_error);
		if (d->cancel)
			done = 1;
		if (!done)
			spc_register_retry(c);
	} while (!done);
	ret = 1;
ex:;
	cam_freeccb(ccb);
	return ret;
}


/* ts B00115 */
/* Return 1 if the given path leads to a regular file or a device that can be
   fseeked, read and eventually written with 2 kB granularity.
*/
int burn_os_is_2k_seekrw(char *path, int flag)
{
        struct stat stbuf;
#ifdef Libburn_DIOCGMEDIASIZE_ISBLK
	int fd, ret;
	off_t add_size;
#else
	char *spt;
	int i, e;
#endif /* ! Libburn_DIOCGMEDIASIZE_ISBLK */

        if (stat(path, &stbuf) == -1)
                return 0;
        if (S_ISREG(stbuf.st_mode))
                return 1;
	if (!S_ISCHR(stbuf.st_mode))
		return 0;

#ifdef Libburn_DIOCGMEDIASIZE_ISBLK

	/* If it throws no error with DIOCGMEDIASIZE then it is a
	   'block device'
	*/
	fd = open(path, O_RDONLY);
	if (fd == -1)
		return 0;
	ret = ioctl(fd, DIOCGMEDIASIZE, &add_size);
	close(fd);

	return (ret != -1);

#else /* Libburn_DIOCGMEDIASIZE_ISBLK */

	spt = strrchr(path, '/');
	if (spt == NULL)
	        spt = path;
	else
	        spt++;
	e = strlen(spt);
	for (i = strlen(spt) - 1; i > 0; i--)
		if (spt[i] >= '0' && spt[i] <= '9')
			e = i;
	if (strncmp(spt, "da", e) == 0) /* SCSI disk. E.g. USB stick. */
		return 1;
	if (strncmp(spt, "cd", e) == 0) /* SCSI CD drive might be writeable. */
		return 1;
	if (strncmp(spt, "ad", e) == 0) /* IDE hard drive */
		return 1;
	if (strncmp(spt, "acd", e) == 0) /* IDE CD drive might be writeable */
		return 1;
	if (strncmp(spt, "fd", e) == 0) /* Floppy disk */
		return 1;
	if (strncmp(spt, "fla", e) == 0) /* Flash drive */
		return 1;
	return 0;

#endif /* ! Libburn_DIOCGMEDIASIZE_ISBLK */

}


/* ts A70909 */
/** Estimate the potential payload capacity of a file address.
    @param path  The address of the file to be examined. If it does not
                 exist yet, then the directory will be inquired.
    @param bytes This value gets modified if an estimation is possible
    @return      -2 = cannot perform necessary operations on file object
                 -1 = neither path nor dirname of path exist
                  0 = could not estimate size capacity of file object
                  1 = estimation has been made, bytes was set
*/
int burn_os_stdio_capacity(char *path, off_t write_start, off_t *bytes)
{
	struct stat stbuf;
	struct statvfs vfsbuf;
	char *testpath = NULL, *cpt;
	off_t add_size = 0;
	int fd, ret;

	BURN_ALLOC_MEM(testpath, char, 4096);
	testpath[0] = 0;
	if (stat(path, &stbuf) == -1) {
		strcpy(testpath, path);
		cpt = strrchr(testpath, '/');
		if(cpt == NULL)
			strcpy(testpath, ".");
		else if(cpt == testpath)
			testpath[1] = 0;
		else
			*cpt = 0;
		if (stat(testpath, &stbuf) == -1)
			{ret = -1; goto ex;}

#ifdef Libburn_if_this_was_linuX

	} else if(S_ISBLK(stbuf.st_mode)) {
		int open_mode = O_RDWR, fd, ret;
		long blocks;

		blocks = *bytes / 512;
		if(burn_sg_open_o_excl)
			open_mode |= O_EXCL;
		fd = open(path, open_mode);
		if (fd == -1)
			{ret = -2; goto ex;}
		ret = ioctl(fd, BLKGETSIZE, &blocks);
		close(fd);
		if (ret == -1)
			{ret = -2; goto ex;}
		*bytes = ((off_t) blocks) * (off_t) 512;

#endif /* Libburn_if_this_was_linuX */


	} else if(S_ISCHR(stbuf.st_mode)) {
		fd = open(path, O_RDONLY);
		if (fd == -1)
			{ret = -2; goto ex;}
		ret = ioctl(fd, DIOCGMEDIASIZE, &add_size);
		close(fd);
		if (ret == -1)
			{ret = -2; goto ex;}
		*bytes = add_size;
	} else if(S_ISREG(stbuf.st_mode)) {
		add_size = burn_sparse_file_addsize(write_start, &stbuf);
		strcpy(testpath, path);
	} else
		{ret = 0; goto ex;}

	if (testpath[0]) {	
		if (statvfs(testpath, &vfsbuf) == -1)
			{ret = -2; goto ex;}
		*bytes = add_size + ((off_t) vfsbuf.f_frsize) *
						(off_t) vfsbuf.f_bavail;
	}
	ret = 1;
ex:
	BURN_FREE_MEM(testpath);
	return ret;
}


/* ts A91122 : an interface to open(O_DIRECT) or similar OS tricks. */

#ifdef Libburn_read_o_direcT

	/* No special O_DIRECT-like precautions are implemented here */

#endif /* Libburn_read_o_direcT */


int burn_os_open_track_src(char *path, int open_flags, int flag)
{
	int fd;

	fd = open(path, open_flags);
	return fd;
}


void *burn_os_alloc_buffer(size_t amount, int flag)
{
	void *buf = NULL;

	buf = calloc(1, amount);
	return buf;
}


int burn_os_free_buffer(void *buffer, size_t amount, int flag)
{
	if (buffer == NULL)
		return 0;
	free(buffer);
	return 1;
}

