/*
 * DATlib   Copyright (c) 1995-1996 Marcus Meissner &
 *          Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *          All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Marcus Meissner
 *    at the Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *
 * 4. The name of the University or the author may not be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND THE AUTHOR ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* $Id: gscsi.c,v 1.3 1997/06/02 18:42:52 msmeissn Exp marcus $
 *
 * $Log: gscsi.c,v $
 * Revision 1.3  1997/06/02 18:42:52  msmeissn
 * added read_buffer,extended_inquiry
 * (Arne Ludwig)
 *
 * Revision 1.2  1997/04/24 10:56:03  msmeissn
 * *** empty log message ***
 *
 * Revision 1.1  1997/03/16 20:28:55  msmeissn
 * Initial revision
 *
 *
 */

/* 
 * Implementation der generic SCSI functions
 */

#include <sys/types.h>
#include <sys/fcntl.h>
#ifdef DAT_SUN
#include <sys/mtio.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int errno;

#include "datlib.h"
#undef SCMD_LOG_SENSE
#include "generic_scsi.h"

static int
rq_sense_out(unsigned char* rqbuf) {
	fprintf(stderr,"Request Sense reports:\n");
	if ((rqbuf[0]&0x7f)!=0x70) {
		fprintf(stderr,"\tInvalid header.\n");
		return -1;
	}
	fprintf(stderr,"\tCurrent command read filemark: %s\n",(rqbuf[2]&0x80)?"yes":"no");
	fprintf(stderr,"\tEarly warning passed: %s\n",(rqbuf[2]&0x40)?"yes":"no");
	fprintf(stderr,"\tIncorrect blocklengt: %s\n",(rqbuf[2]&0x20)?"yes":"no");
	fprintf(stderr,"\tSense Key: %d\n",rqbuf[2]&0xf);
	if (rqbuf[0]&0x80)
		fprintf(stderr,"\tResidual Length: %d\n",rqbuf[3]*0x1000000+rqbuf[4]*0x10000+rqbuf[5]*0x100+rqbuf[6]);
	fprintf(stderr,"\tAdditional Sense Length: %d\n",rqbuf[7]);
	fprintf(stderr,"\tAdditional Sense Code: %d\n",rqbuf[12]);
	fprintf(stderr,"\tAdditional Sense Code Qualifier: %d\n",rqbuf[13]);
	if (rqbuf[15]&0x80) {
		fprintf(stderr,"\tIllegal Param is in %s\n",(rqbuf[15]&0x40)?"the CDB":"the Data Out Phase");
		if (rqbuf[15]&0x8) {
			fprintf(stderr,"Pointer at %d, bit %d\n",rqbuf[16]*256+rqbuf[17],rqbuf[15]&0x7);
		}
	}
	return 0;
}


int gscsi_create_partition(int fd,int size,int size_kind);
int gscsi_delete_partition(int fd);
int gscsi_switch_active_partition(int fd,int targetpart);
int gscsi_query_active_partition(int fd);
int gscsi_set_compression(int fd,int compression);
int gscsi_set_decompression(int fd,int decompression);
int gscsi_query_compression(int fd);
int gscsi_query_decompression(int fd);
int gscsi_get_log_page(int fd,int nr,unsigned char *logpage,int logsize);
int gscsi_get_mode_page(int fd,int nr,unsigned char *modepage,int modesize);
int gscsi_set_mode_page(int fd,unsigned char *modepage,int modesize);
int gscsi_get_inquiry(int fd,unsigned char *inqbuf,int inqsize);
int gscsi_request_sense(int fd,unsigned char *inqbuf,int inqsize);
int gscsi_get_inquiry_extended(int fd,int page,unsigned char *inqbuf,int inqsize);
int gscsi_read_buffer(int fd,int memid,unsigned long memoff,unsigned char *buf,int size);

static struct datlib_functions datlib_gscsi_funs={
	"gscsi",
	NULL,
	gscsi_create_partition,
	gscsi_delete_partition,
	gscsi_switch_active_partition,
	gscsi_query_active_partition,
	gscsi_set_compression,
	gscsi_set_decompression,
	gscsi_query_compression,
	gscsi_query_decompression,
	gscsi_get_log_page,
	gscsi_get_mode_page,
	gscsi_set_mode_page,
	gscsi_get_inquiry,
	gscsi_request_sense,
	gscsi_get_inquiry_extended,
	gscsi_read_buffer,
};

struct datlib_functions *
datlib_gscsi_init(int fd) {
	unsigned char	inqbuf[100];

	if (fd<0) {
		errno=EBADF;
		return NULL;
	}
	if (-1==gscsi_get_inquiry(fd,inqbuf,100))
		return NULL;
#if 0
	if (inqbuf[0]!=1) { /* 1 is SCSI SEQUENTIAL ACCESS DEVICE */
		fprintf(stderr,"uscsicmd.c:init:Not a sequential access device (%d).\n",inqbuf[0]);
		errno=ENOTTY;
		return NULL;
	}
	if ((inqbuf[2]&0x3)!=2) {
		fprintf(stderr,"uscsicmd.c:init:Need to operate in SCSI-2 mode.\n");
		errno=ENOTTY;
		return NULL;
	}
#endif
	if (strncmp((char*)inqbuf+8,"ARCHIVE Python ",strlen("ARCHIVE Python"))) {
		inqbuf[inqbuf[4]+5]='\0';/* terminate string */
/*		fprintf(stderr,"uscsicmd.c:init:Warning: Drive is not a ARCHIVE Python (%s detected), but continuing anyway.\n",inqbuf+4);*/
	}
	return &datlib_gscsi_funs;
}

enum drivetype { DRIVE_HP,DRIVE_ARCHIVE,DRIVE_SONY };

enum drivetype
gscsi_get_drivetype(int fd) {
	char	buf[100];

	if (-1==gscsi_get_inquiry(fd,buf,100))
		return -1;
	if (!strncmp((char*)buf+8,"HP ",strlen("HP ")))
		return DRIVE_HP;
	if (!strncmp((char*)buf+8,"SONY ",strlen("SONY ")))
		return DRIVE_SONY;
	if (!strncmp((char*)buf+8,"ARCHIVE ",strlen("ARCHIVE ")))
		return DRIVE_ARCHIVE;
	return 0;
}

int 
gscsi_create_partition(int fd,int size,int size_kind) {
	char			cdb[6];
	char			buf[100];
	char			rqbuf[100];

	enum	drivetype	dt=gscsi_get_drivetype(fd);

	if (size_kind<SIZE_BYTE || size_kind>SIZE_MBYTE)
		RETURN(-EINVAL);
	switch (dt) {
	default:
		if (size_kind!=SIZE_MBYTE) {
			fprintf(stderr,"gscsi_create_partition: drive only supports MByte sized partitions.\n");
			return -1;
		}
		break;
	case DRIVE_ARCHIVE:
		/* supports all types */
		break;
	}
	if (size<=0 || size>65535)
		RETURN(-EINVAL);
	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x11;/* medium partition page */
	cdb[3]=0x00;
	cdb[4]=  27;
	cdb[5]=0x00;

	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,NULL,0))
		RETURN(-errno);

	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10; /* interpret as SCSI-2 command */
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=0x00;/* paralist length added l8er */
	cdb[5]=0x00;
	/* build paramlist */
	/* Parameter List Header */
	buf[0]=0x00;

	/* this is a mode page */
	buf[15]=0x01;/* nr. of partitions ... */
	buf[16]=0x20|(size_kind<<3);/* DO PARTITIONING, depending on kind */

	switch (dt) {
	default: /* possible FIXME */
	case DRIVE_HP:
	case DRIVE_ARCHIVE:
		buf[13]=0x08; /* length */
		buf[20]=(size>>8)&0xff;
		buf[21]=size&0xff;/* 1 Megabyte */
		cdb[4]=22;
		break;
	case DRIVE_SONY: {
		int	ysize,xsize;

		buf[13]=0x0a;
		cdb[4]	= 24;

		/* determine complete size */
		/* 20,21 - first, 22,23 - second partition */
		xsize = buf[20]*256+buf[21]+buf[22]*256+buf[23];
		ysize = xsize-size;
		buf[20] = ysize>>8;
		buf[21] = ysize&0xff;
		buf[22] = size>>8;
		buf[23] = size&0xff;
		break;
	}
	}

	if (-1==send_scsi_command(fd,cdb,6,NULL,0,buf,cdb[4],NULL,0))
		RETURN(-errno);
	return 0; /* SUCCESS */
}

int
gscsi_delete_partition(int fd) {
	char			cdb[6];
	char			buf[100];
	char			rqbuf[100];
	enum drivetype		dt=gscsi_get_drivetype(fd);
	int			res;

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x11;/* medium partition page */
	cdb[3]=0x00;
	cdb[4]=  27;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,rqbuf,100)) {
		int	saveerrno=errno;
		rq_sense_out(rqbuf);
		errno=saveerrno;
		return	-1;
	}
	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10; /* interpret as SCSI-2 command */
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=22;
	cdb[5]=0x00;
	/* build paramlist */
	/* Parameter List Header,
	 * just set byte 0 to 0, the rest stays the same
	 */
	buf[0]=	0x00;
	/* this is a mode page, starting at offset 12 */
	buf[15]	=0x00;	/* nr. of partitions ... 0 means delete*/
	buf[16]|=0x20;	/* DO PARTITIONING */
	cdb[4] =  22;
	switch (dt) {
	case DRIVE_ARCHIVE:
		buf[13]	=0x06;
		cdb[4]	=20;
		break;
	case DRIVE_SONY: {
		int	ysize,xsize;

		xsize = buf[20]*256+buf[21]+buf[22]*256+buf[23];
		buf[20] = xsize>>8;
		buf[21] = xsize&0xff;
		buf[22] = buf[23] =0;
		buf[13]	=0x0A;
		cdb[4] =  24;
		break;
	}
	default:
		buf[20]	=0x00;
		buf[21]	=0x00;
		break;
	}

	if (-1==send_scsi_command(fd,cdb,6,NULL,0,buf,cdb[4],rqbuf,100)) {
		int	saveerrno=errno;
		rq_sense_out(rqbuf);
		errno=saveerrno;
		return	-1;
	}
	return 0; /* success */
}

int
gscsi_switch_active_partition(int fd,int target) {
	unsigned char	cdb[6];
	unsigned char	buf[100];
	unsigned char	rqbuf[100];

	if (target<0 || target>1)
		RETURN(-EINVAL);
	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x10;/* device configuration page */
	cdb[3]=0x00;
	cdb[4]=  27;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,rqbuf,100)) {
		return -1; /* errno already set */
	}
	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=  27;
	cdb[5]=0x00;

	buf[0]=0;
	buf[14]|=0x40;	/* Change Active Partition */
	buf[15]=target;/* target partition */
	cdb[4]=28;

	if (-1==send_scsi_command(fd,cdb,6,NULL,0,buf,28,rqbuf,100)) {
		int	saveerrno=errno;
		rq_sense_out(rqbuf);
		errno=saveerrno;
		return -1; /* errno set */
	}
	return 0; /* success */
}

int
gscsi_query_active_partition(int fd) {
	unsigned char	cdb[6];
	unsigned char	buf[100];

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x10;/* device configuration page */
	cdb[3]=0x00;
	cdb[4]=  27;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,NULL,0)) {
		return -1; /* errno already set */
	}
	return buf[15]; /* partition nr */
}

int
gscsi_set_compression(int fd,int compress) {
	unsigned char		cdb[6];
	unsigned char		buf[100];
	unsigned char		rqbuf[100];

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x0F;
	cdb[3]=0x00;
	cdb[4]=100;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,rqbuf,100))
		return -1; /* errno already set */

	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=0x00;
	cdb[5]=0x00;

	buf[0]=0x00;/* need to reset that */
	buf[2]|=0x10;
	/* only change values we need */

	if (compress)
		buf[14]|= 0x80;
	else
		buf[14]&=~0x80;
	if (compress==1)
		compress=0x20;
	buf[19]=compress;
	cdb[4]=28;

	if (-1==send_scsi_command(fd,cdb,6,NULL,0,buf,28,NULL,0))
		return -1;
	return 0;
}

int
gscsi_set_decompression(int fd,int decompress) {
	unsigned char		cdb[6];
	unsigned char		buf[100];

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x0F;
	cdb[3]=0x00;
	cdb[4]=100;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,NULL,0))
		return -1; /* errno already set */

	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=0x00;
	cdb[5]=0x00;

	buf[0]=0x00;/* need to reset that */
	/* only change values we need */
	buf[15]=decompress?0x80:0x00;/* Data Decompression Enable */
	buf[15]|=0x20;/*report error on decomp */
	buf[23]=decompress;/* default decompression */
	cdb[4]=28; /*29 ?*/

	if (-1==send_scsi_command(fd,cdb,6,NULL,0,buf,28,NULL,0))
		return -1;
	return 0;
}

int
gscsi_query_compression(int fd) {
	unsigned char		cdb[6];
	unsigned char		buf[100];

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x0F;
	cdb[3]=0x00;
	cdb[4]=100;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,NULL,0))
		return -1; /* errno already set */
	return (buf[14]&0x80)?buf[19]:0;
}

int
gscsi_query_decompression(int fd) {
	unsigned char	cdb[6];
	unsigned char	buf[100];

	memset(buf,'\0',100);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x0F;
	cdb[3]=0x00;
	cdb[4]=100;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,100,NULL,0,NULL,0))
		return -1; /* errno already set */
	return (buf[15]&0x80)?buf[23]:0;
}

int
gscsi_get_log_page(int fd,int nr,unsigned char *logpage,int logsize) {
	unsigned char	cdb[10];
	unsigned char	*buf;

	if (logsize<=0)
		RETURN(-EINVAL);
	if (logsize>4095) logsize=4095;
	buf=(unsigned char*)malloc(logsize);
	memset(buf,'\0',logsize);
	cdb[0]=SCMD_LOG_SENSE;
	cdb[1]=0x00;
	cdb[2]=  nr|0x40;	/* current cumulative values */
	cdb[3]=0x00;
	cdb[4]=0x00;
	cdb[5]=0x00;
	cdb[6]=0x00;
	cdb[7]=logsize>>8;
	cdb[8]=logsize&0xff;
	cdb[9]=0x00;
	if (-1==send_scsi_command(fd,cdb,10,buf,logsize,NULL,0,NULL,0))
		return -1; /* errno already set */
	memcpy(logpage,buf,logsize);
	free(buf);
	return logsize;
}

int
gscsi_set_mode_page(int fd,unsigned char *modepage,int modesize) {
	unsigned char	cdb[6];

	if (modesize<=0)
		RETURN(-EINVAL);
	if (modesize>255) modesize=255;

	if (modesize>modepage[0]/*-4-modepage[3]*/);
		modesize=modepage[0]/*-4-modepage[3]*/;
	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x00;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=modesize;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,NULL,0,modepage,modesize,NULL,0))
		return -1; /* errno already set */
	return 0;
}

int
gscsi_get_mode_page(int fd,int nr,unsigned char *modepage,int modesize) {
	unsigned char	cdb[6];
	unsigned char	*buf;

	if (modesize<=0)
		RETURN(-EINVAL);
	if (modesize>255) modesize=255;

	buf=(unsigned char*)malloc(modesize);
	memset(buf,'\0',modesize);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=nr;
	cdb[3]=0x00;
	cdb[4]=modesize;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,buf,modesize,NULL,0,NULL,0))
		return -1; /* errno already set */
	/* check size */
	if (modesize>buf[0]+1);
		modesize=buf[0]+1;
	memcpy(modepage,buf,modesize);
	free(buf);
	return modesize;
}

int
gscsi_get_inquiry(int fd,unsigned char *inqbuf,int inqlen) {
	unsigned char	cdb[6];

	if (inqlen<0)	return EINVAL;
	if (inqlen>255)	inqlen=255;
	memset(inqbuf,'\0',inqlen);
	cdb[0]=SCMD_INQUIRY;	
	cdb[1]=0x00;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=inqlen;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,inqbuf,inqlen,NULL,0,NULL,0))
		return -1;
	return 0;
}

int
gscsi_get_inquiry_extended(int fd,int page,unsigned char *inqbuf,int inqlen) {
	unsigned char   cdb[6];

	if (inqlen<0)   return EINVAL;
	if (inqlen>255) inqlen=255;
	memset(inqbuf,'\0',inqlen);
	cdb[0]=SCMD_INQUIRY;    
	cdb[1]=0x01;
	cdb[2]=page&0xff;
	cdb[3]=0x00;
	cdb[4]=inqlen;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,inqbuf,inqlen,NULL,0,NULL,0))
		return -1;
	return 0;
}

int
gscsi_read_buffer(int fd,int memid, unsigned long memoff, unsigned char *buf,int len) {
	unsigned char   cdb[10];

	if (len<0)      return EINVAL;
	if (len>255)    len=255;
	memset(buf,'\0',len);
	cdb[0]=SCMD_READ_DATA_BUFFER;   
	cdb[1]=0x01;
	cdb[2]=memid;
	cdb[3]=memoff/65536;
	cdb[4]=(memoff%65536)/256;
	cdb[5]=memoff%256;
	cdb[6]=0x00;
	cdb[7]=0x00;
	cdb[8]=len;
	cdb[9]=0x00;
	if (-1==send_scsi_command(fd,cdb,10,buf,len,NULL,0,NULL,0))
		return -1;
	return 0;
}

int
gscsi_request_sense(int fd,unsigned char *inqbuf,int inqlen) {
	unsigned char	cdb[6];

	if (inqlen<0)	return EINVAL;
	if (inqlen>255)	inqlen=255;
	memset(inqbuf,'\0',inqlen);
	cdb[0]=SCMD_REQUEST_SENSE;	
	cdb[1]=0x00;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=inqlen;
	cdb[5]=0x00;
	if (-1==send_scsi_command(fd,cdb,6,inqbuf,inqlen,NULL,0,NULL,0))
		return -1;
	return inqbuf[7]+8; /* overall sense length */
}
