/*
 * Native implementation of cdrdao's SCSI interface for NetBSD.
 * 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 and NetBSD 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 <ctype.h>
#include <string.h>
#include <errno.h>
#include <sys/scsiio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
/* avoid ../trackdb/util.h */
#include "/usr/include/util.h"
#include <unistd.h>

#include "ScsiIf.h"

/* can't include trackdb/util.h */
extern void message(int level, const char *fmt, ...);

#include "decodeSense.cc"

#define MAX_SCAN 32

class ScsiIfImpl {
public:
	char *name_;
	int fd_;
	long timeout_; /* in ms */
	struct scsireq screq_;
	char *error_;
};

ScsiIf::ScsiIf(const char *name)
{
#define PREFIX "/dev/"
	int len;

	impl_ = new ScsiIfImpl;
	len = strlen(name);
	if (len == 0) {
		impl_->name_ = NULL;
	} 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 if (isdigit(name[len-1]) &&
	           (strncmp(name, "sd", 2) == 0 ||
	            strncmp(name, "rsd", 3) == 0 ||
	            strncmp(name, "cd", 2) == 0 ||
	            strncmp(name, "rcd", 3) == 0)) {
		impl_->name_ = new char[strlen(PREFIX) + len + 1 + 1];
		strcpy(impl_->name_, PREFIX);
		strcat(impl_->name_, name);
		impl_->name_[strlen(PREFIX) + len] = 'a' + getrawpartition();
		impl_->name_[strlen(PREFIX) + len + 1] = '\0';
	} else {
		impl_->name_ = new char[strlen(PREFIX) + len + 1];
		strcpy(impl_->name_, PREFIX);
		strcat(impl_->name_, name);
	}
	impl_->fd_ = -1;
	impl_->timeout_ = 5*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;
}

// sends a scsi command and receives data
// return 0: OK
//        1: scsi command failed (os level, no sense data available)
//        2: scsi command failed (sense data available)
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_->screq_.cmdlen = 0;
	impl_->screq_.databuf = NULL; impl_->screq_.datalen = 0;
	impl_->screq_.flags = 0;
	impl_->screq_.timeout = impl_->timeout_;
	if (cmdLen > 16) ERROR("cmdLen > 16");
	if (cmdLen == 0) ERROR("cmdLen == 0");
	memcpy(impl_->screq_.cmd, cmd, cmdLen);
	impl_->screq_.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_->screq_.flags |= SCCMD_WRITE;
		impl_->screq_.databuf = (caddr_t)dataOut;
		impl_->screq_.datalen = dataOutLen;
	} else if (dataIn != NULL) {
		if (dataInLen == 0) ERROR("dataInLen == 0");
		if (dataOutLen !=0) ERROR("dataOutLen != 0");
		impl_->screq_.flags |= SCCMD_READ;
		impl_->screq_.databuf = (caddr_t)dataIn;
		impl_->screq_.datalen = dataInLen;
	} else {
		if (dataOutLen !=0 || dataInLen != 0) ERROR("dataLen != 0");
		impl_->screq_.databuf = NULL;
		impl_->screq_.datalen = 0;
	}
	impl_->screq_.senselen = SENSEBUFLEN;
	if (ioctl(impl_->fd_, SCIOCCOMMAND, &impl_->screq_) < 0) {
		char str[80];
		strcpy(str, "SCIOCOMMAND: "); strcat(str, strerror(errno));
		ERROR(str);
	}
	if (impl_->screq_.retsts == SCCMD_OK && impl_->screq_.status == 0) 
		return 0;
	else if (impl_->screq_.retsts == SCCMD_SENSE && impl_->screq_.status == 0)  {
		if (showMessage) printError();
		return 1;
	} else {
		if (showMessage) printError();
		return 2;
	}
#undef ERROR
}

const unsigned char *ScsiIf::getSense(int &len) const
{
	len = impl_->screq_.senselen_used;
	return impl_->screq_.sense;
}

void ScsiIf::printError()
{
	if (impl_->screq_.cmdlen > 0) {
		char s[80];
		char *p = s;
		int i;
		p += snprintf(p, s + sizeof(s) - p, "CDB=");
		for (i = 0; i < impl_->screq_.cmdlen; i++) {
			p += snprintf(p, s + sizeof(s) - p, "%.2X ", impl_->screq_.cmd[i]);
		}
		p[-1] = ',';
		switch (impl_->screq_.flags & (SCCMD_READ | SCCMD_WRITE)) {
			case SCCMD_READ: p += snprintf(p, s + sizeof(s) - p, " RD"); break;
			case SCCMD_WRITE: p += snprintf(p, s + sizeof(s) - p, " WR"); break;
			case SCCMD_READ | SCCMD_WRITE: p += snprintf(p, s + sizeof(s) - p, " RW"); break;
		}
		p += snprintf(p, s + sizeof(s) - p, ", BUF=%p", impl_->screq_.databuf);
		p += snprintf(p, s + sizeof(s) - p, ", LEN=%lu", impl_->screq_.datalen);
		p += snprintf(p, s + sizeof(s) - p, ", TO=%lu", impl_->screq_.timeout);
		message(-2, s);
	}
	if (impl_->error_ != NULL) {
		message(-2, impl_->error_);
	} else switch (impl_->screq_.retsts) {
		case SCCMD_OK: switch (impl_->screq_.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;
			default: message(-2, "undefined status");
		} break;
		case SCCMD_TIMEOUT: message(-2, "timeout"); break;
		case SCCMD_BUSY: message(-2, "busy"); break;
		case SCCMD_SENSE: decodeSense(impl_->screq_.sense,
		                              impl_->screq_.senselen_used); break;
		case SCCMD_UNKNOWN: message(-2, "unknown error"); break;
		default: message(-2, "undefined retsts"); break;
	}
}

int inq(int fd, char *vend, char *prod, char *rev)
{
	char buf[44];
	struct scsireq screq = {
		/* flags */ SCCMD_READ,
		/* timeout */ 1000,
		/* cmd */ {0x12, 0, 0, 0, sizeof(buf), 0},
		/* cmdlen */ 6,
		/* databuf */ (caddr_t)&buf,
		/* datalen */ sizeof(buf),
		/* datalen_used */ 0,
		/* sense */ {},
		/* senselen */ SENSEBUFLEN,
		/* senselen_used */ 0,
		/* status */ 0,
		/* retsts */ 0,
		/* error */ 0
	};
	char *p, *q;

	if (ioctl(fd, SCIOCCOMMAND, &screq) < 0 ||
	          screq.status != 0 ||
	          screq.retsts != SCCMD_OK) {
		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_, vendor_, product_, revision_);
}

ScsiIf::ScanData *ScsiIf::scan(int *len)
{
	DIR *dirp;
	struct dirent *dp;
	char c;
	int l;
	int fd;
	struct scsi_addr saddr;
	ScanData *scanData;
	char *s;

	scanData = new ScanData[MAX_SCAN]; *len = 0;
	c = 'a' + getrawpartition();
	if ((dirp = opendir("/dev")) == 0) { *len = 0; return NULL; }
	while ((dp = readdir(dirp)) != NULL) {
		l = strlen(dp->d_name);
		if (*len < MAX_SCAN && l > 2 &&
		    (((strncmp(dp->d_name, "rsd", 3) == 0 ||
		       strncmp(dp->d_name, "rcd", 3) == 0) &&
		       isdigit(dp->d_name[l-2]) && dp->d_name[l-1] == c) ||
		    ((strncmp(dp->d_name, "enrst", 5) == 0 ||
		      strncmp(dp->d_name, "ch", 2) == 0 ||
		      strncmp(dp->d_name, "enss", 4) == 0 ||
		      strncmp(dp->d_name, "uk", 2) == 0) &&
		      isdigit(dp->d_name[l-1])))) {
			s = new char[5 + l + 1];
			strcpy(s, "/dev/");
			strcat(s, dp->d_name);
			if ((fd = open(s, O_RDWR, 0)) >= 0) {
				if (ioctl(fd, SCIOCIDENTIFY, &saddr) >= 0) {
					switch (saddr.type) {
						case TYPE_SCSI:
							scanData[*len].bus = saddr.addr.scsi.scbus;
							scanData[*len].id = saddr.addr.scsi.target;
							scanData[*len].lun = saddr.addr.scsi.lun;
							break;
						case TYPE_ATAPI:
							scanData[*len].bus = saddr.addr.atapi.atbus;
							scanData[*len].id = saddr.addr.atapi.drive;
							scanData[*len].lun = -1;
							break;
						default:
							scanData[*len].bus =
							scanData[*len].id =
							scanData[*len].lun = -1;
					}
					if (inq(fd, scanData[*len].vendor,
					            scanData[*len].product,
					            scanData[*len].revision) == 0) (*len)++;
				}
				(void)close(fd);
			}
			delete[] s;
		}
	}
	closedir(dirp);
	return scanData;
}

#include "ScsiIf-common.cc"
