/*
 * Native implementation of cdrdao's SCSI interface for Irix 6.
 * Copyright (C) by Edgar Fuß, Bonn, May 2003, July 2007.
 * Do with this whatever you like, as long as you are either me or you keep
 * this message intact and both
 * - acknowledge that I wrote it for cdrdao in the first place, and
 * - don't blame me if it doesn't do what you like or expect.
 * These routines do exactly what they do. If that's not what you expect them
 * or would like them to do, don't complain with me, the cdrdao project, my
 * neighbour's brother-in-law or anybody else, but rewrite them to your taste.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <sys/dsreq.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

#include "ScsiIf.h"

extern void message(int level, const char *fmt, ...);

#include "decodeSense.cc"

#define MAX_SCAN 32
#define SENSE_MAX 256

class ScsiIfImpl {
	public:
	char *name_;
	int fd_;
	long timeout_; /* in ms */
	struct dsreq dsreq_;
	char *error_;
	unsigned char sensebuf_[SENSE_MAX];
};

ScsiIf::ScsiIf(const char *name)
{
#define PREFIX "/dev/scsi/"
#define PREFIX_SC "/dev/scsi/sc"
	int len;
	int bus, targ, lun, count;

	impl_ = new ScsiIfImpl;
	len = strlen(name);
	if (len == 0) {
		impl_->name_ = NULL;
	} else if (sscanf(name, "%i,%i,%i%n", &bus, &targ, &lun, &count) == 3 &&
	           count == len) {
		if ((bus < 0) || (targ < 0) || (lun < 0))
			impl_->name_ = NULL;
		else {
			impl_->name_ = new char[strlen(PREFIX_SC) + len + 1];
			sprintf(impl_->name_, "%s%dd%dl%d", PREFIX_SC, bus, targ, lun);
		}
	} else if (strncmp(name, "/", 1) == 0 ||
	           strncmp(name, "./", 2) == 0 ||
	           strncmp(name, "../", 3) == 0) {
		impl_->name_ = new char[len + 1];
		strcpy(impl_->name_, name);
	} else {
		impl_->name_ = new char[strlen(PREFIX) + len + 1];
		strcpy(impl_->name_, PREFIX);
		strcat(impl_->name_, name);
	}
	impl_->fd_ = -1;
	impl_->timeout_ = 10*1000;
	impl_->error_ = NULL;

	maxDataLen_ = 64 * 1024;
	vendor_[0] = 0;
	product_[0] = 0;
	revision_[0] = 0;
#undef PREFIX
}

ScsiIf::~ScsiIf()
{
	if (impl_->fd_ >= 0) (void)close(impl_->fd_);
	if (impl_->name_ != NULL) delete[] impl_->name_;
	if (impl_->error_ != NULL) delete[] impl_->error_;
	delete impl_;
}

int ScsiIf::init()
{
	if (impl_->name_ == NULL) return 1;
	if ((impl_->fd_ = open(impl_->name_, O_RDWR, 0)) < 0) {
		message(-2, "init: %s", strerror(errno));
		return 1;
	}
	if (inquiry()) return 2;
	return 0;
}

int ScsiIf::timeout(int t)
{
	int ret = impl_->timeout_/1000;
	impl_->timeout_ = t*1000;
	return ret;
}

int ScsiIf::sendCmd(const unsigned char *cmd, int cmdLen, 
                    const unsigned char *dataOut, int dataOutLen,
                    unsigned char *dataIn, int dataInLen,
                    int showMessage)
{
#define ERROR(msg) {\
	impl_->error_ = new char[9 + strlen(msg) + 1];\
	strcpy(impl_->error_, "sendCmd: ");\
	strcat(impl_->error_, msg);\
	if (showMessage) printError();\
	return 1;\
}

	if (impl_->error_ != NULL) { delete[] impl_->error_; impl_->error_ = NULL; }
	/* for printError: */
	impl_->dsreq_.ds_cmdbuf = NULL; impl_->dsreq_.ds_cmdlen = 0;
	impl_->dsreq_.ds_databuf = NULL; impl_->dsreq_.ds_datalen = 0;
	impl_->dsreq_.ds_flags = 0;
	impl_->dsreq_.ds_time = impl_->timeout_;
	if (cmdLen == 0) ERROR("cmdLen == 0");
	if (cmdLen > 255) ERROR("cmdLen > 255");
	impl_->dsreq_.ds_cmdbuf = (caddr_t)cmd;
	impl_->dsreq_.ds_cmdlen = cmdLen;
	if (dataOut != NULL && dataIn != NULL) {
		ERROR("Out and In");
	} else if (dataOut != NULL) {
		if (dataOutLen == 0) ERROR("dataOutLen == 0");
		if (dataInLen != 0) ERROR("dataInLen != 0");
		impl_->dsreq_.ds_flags |= DSRQ_WRITE;
		impl_->dsreq_.ds_databuf = (caddr_t)dataOut;
		impl_->dsreq_.ds_datalen = dataOutLen;
	} else if (dataIn != NULL) {
		if (dataInLen == 0) ERROR("dataInLen == 0");
		if (dataOutLen !=0) ERROR("dataOutLen != 0");
		impl_->dsreq_.ds_flags |= DSRQ_READ;
		impl_->dsreq_.ds_databuf = (caddr_t)dataIn;
		impl_->dsreq_.ds_datalen = dataInLen;
	} else {
		if (dataOutLen !=0 || dataInLen != 0) ERROR("dataLen != 0");
		impl_->dsreq_.ds_databuf = NULL;
		impl_->dsreq_.ds_datalen = 0;
	}
	impl_->dsreq_.ds_senselen = SENSE_MAX;
	impl_->dsreq_.ds_sensebuf = (caddr_t)impl_->sensebuf_;
	impl_->dsreq_.ds_flags |= DSRQ_SENSE;
	if (ioctl(impl_->fd_, DS_ENTER, &impl_->dsreq_) < 0) {
		char str[80];
		strcpy(str, "DS_ENTER: "); strcat(str, strerror(errno));
		ERROR(str);
	}
	if (impl_->dsreq_.ds_ret == 0 ||
	    impl_->dsreq_.ds_ret == DSRT_OK ||
	    impl_->dsreq_.ds_ret == DSRT_SHORT)
		return 0;
	else if (impl_->dsreq_.ds_ret == DSRT_SENSE)  {
		if (showMessage) printError();
		return 1;
	} else {
		if (showMessage) printError();
		return 2;
	}
#undef ERROR
}

const unsigned char *ScsiIf::getSense(int &len) const
{
	len = impl_->dsreq_.ds_sensesent;
	return impl_->sensebuf_;
}

void ScsiIf::printError()
{
	if (impl_->dsreq_.ds_cmdbuf != NULL) {
		char s[80];
		char *p = s;
		int i;
		p += snprintf(p, s + sizeof(s) - p, "CDB=");
		for (i = 0; i < impl_->dsreq_.ds_cmdlen; i++) {
			p += snprintf(p, s + sizeof(s) - p, "%.2X ", impl_->dsreq_.ds_cmdbuf[i]);
		}
		p[-1] = ',';
		switch (impl_->dsreq_.ds_flags & (DSRQ_READ | DSRQ_WRITE)) {
			case DSRQ_READ: p += snprintf(p, s + sizeof(s) - p, " RD"); break;
			case DSRQ_WRITE: p += snprintf(p, s + sizeof(s) - p, " WR"); break;
			case DSRQ_READ | DSRQ_WRITE: p += snprintf(p, s + sizeof(s) - p, " RW"); break;
		}
		p += snprintf(p, s + sizeof(s) - p, ", BUF=%p", impl_->dsreq_.ds_databuf);
		p += snprintf(p, s + sizeof(s) - p, ", LEN=%lu", impl_->dsreq_.ds_datalen);
		p += snprintf(p, s + sizeof(s) - p, ", TO=%lu", impl_->dsreq_.ds_time);
		message(-2, s);
	}
	if (impl_->error_ != NULL) {
		message(-2, impl_->error_);
	} else {
		switch (impl_->dsreq_.ds_ret) {
			case DSRT_DEVSCSI: message(-2,"devscsi failure"); break;
			case DSRT_MULT:    message(-2,"request rejected"); break;
			case DSRT_CANCEL:  message(-2,"request cancelled"); break;
			case DSRT_REVCODE: message(-2,"obsolete"); break;
			case DSRT_AGAIN:   message(-2,"try again"); break;
			case DSRT_HOST:    message(-2,"host failure"); break;
			case DSRT_NOSEL:   message(-2,"no select"); break;
			case DSRT_SHORT:   message(-2,"short transfer"); break;
			case DSRT_OK:      message(-2,"complete transfer"); break;
			case DSRT_SENSE:   /* message(-2,"sense"); */
				decodeSense(impl_->sensebuf_,
				            impl_->dsreq_.ds_sensesent);
				break;
			case DSRT_NOSENSE: message(-2,"sense error"); break;
			case DSRT_TIMEOUT: message(-2,"timeout"); break;
			case DSRT_LONG:    message(-2,"overrun"); break;
			case DSRT_PROTO:   message(-2,"protocol failure"); break;
			case DSRT_EBSY:    message(-2,"busy lost"); break;
			case DSRT_REJECT:  message(-2,"message reject"); break;
			case DSRT_PARITY:  message(-2,"parity error"); break;
			case DSRT_MEMORY:  message(-2,"memory error"); break;
			case DSRT_CMDO:    message(-2,"command error"); break;
			case DSRT_STAI:    message(-2,"status error"); break;
			case DSRT_UNIMPL:  message(-2,"not implemented"); break;
			default: message(-2, "undefined ds_ret"); break;
		}
		switch (impl_->dsreq_.ds_status) {
			case 0x00: message(-2, "GOOD"); break;
			case 0x02: message(-2, "CHECK CONDITION"); break;
			case 0x04: message(-2, "CONDITION MET"); break;
			case 0x08: message(-2, "BUSY"); break;
			case 0x10: message(-2, "INTERMEDIATE"); break;
			case 0x14: message(-2, "INTERMEDIATE, CONDITION MET"); break;
			case 0x18: message(-2, "RESERVATION CONFLICT"); break;
			case 0x22: message(-2, "COMMAND TERMINATED"); break;
			case 0x28: message(-2, "QUEUE FULL"); break;
			case 0xff: break;
			default: message(-2, "undefined status");
		}
	}
}

int inq(int fd, void *sensebuf, int senselen, char *vend, char *prod, char *rev)
{
	char buf[44];
	char cmd[] = {0x12, 0, 0, 0, sizeof(buf), 0};
	struct dsreq dsreq = {
		/* flags */ DSRQ_READ,
		/* time */ 1000,
		/* private */ 0,
		/* cmdbuf */ (caddr_t)cmd,
		/* cmdlen */ sizeof(cmd),
		/* databuf */ (caddr_t)buf,
		/* datalen */ sizeof(buf),
		/* sensebuf */ (caddr_t)sensebuf,
		/* senselen */ senselen,
		/* iovbuf */ NULL,
		/* iovlen */ 0,
		/* link */ NULL,
		/* sync */ 0,
		/* revcode */ 0,
		/* ret */ 0,
		/* status */ 0,
		/* msg */ 0,
		/* cmdsent */ 0,
		/* datasent */ 0,
		/* sensesent */ 0
	};
	char *p, *q;

	if (sensebuf != NULL && senselen > 0)
		dsreq.ds_flags |= DSRQ_SENSE;
	if (ioctl(fd, DS_ENTER, &dsreq) < 0 ||
	    (dsreq.ds_ret != 0 && dsreq.ds_ret != DSRT_OK &&
	    dsreq.ds_ret != DSRT_SHORT)) {
		vend[0] = prod[0] = rev[0] = '\0';
		return 1;
	}
	p = buf + 8; q = buf + 16; while (q > p && q[-1] == ' ') q--;
	memcpy(vend, p, q - p); vend[q - p] = '\0';
	p = buf + 16; q = buf + 32; while (q > p && q[-1] == ' ') q--;
	memcpy(prod, p, q - p); prod[q - p] = '\0';
	p = buf + 32; q = buf + 36; while (q > p && q[-1] == ' ') q--;
	memcpy(rev, p, q - p); rev[q - p] = '\0';
	return 0;
}

int ScsiIf::inquiry()
{
	return inq(impl_->fd_, impl_->sensebuf_, SENSE_MAX, vendor_, product_, revision_);
}

ScsiIf::ScanData *ScsiIf::scan(int *len)
{
	DIR *dirp;
	struct dirent *dp;
	int l;
	int fd;
	ScanData *scanData;
	char s[35];
	int bus, targ, lun, count;

	scanData = new ScanData[MAX_SCAN]; *len = 0;
	if ((dirp = opendir("/dev/scsi")) == 0) { *len = 0; return NULL; }
	while ((dp = readdir(dirp)) != NULL) {
		l = strlen(dp->d_name);
		if (*len < MAX_SCAN && l > 2 &&
		    sscanf(dp->d_name, "sc%dd%dl%d%n", &bus, &targ, &lun, &count) == 3 &&
		    count == l) {
			scanData[*len].bus = bus;
			scanData[*len].id = targ;
			scanData[*len].lun = lun;
			strcpy(s, "/dev/scsi/");
			strcat(s, dp->d_name);
			if ((fd = open(s, O_RDWR, 0)) >= 0) {
				if (inq(fd, NULL, 0,
				    scanData[*len].vendor,
				    scanData[*len].product,
				    scanData[*len].revision) == 0) (*len)++;
			}
			(void)close(fd);
		}
	}
	closedir(dirp);
	return scanData;
}

#include "ScsiIf-common.cc"
