#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <scsi/scsi.h>
#include <linux/major.h>
#include <linux/cciss_ioctl.h>

/* #include <scsi/scsi_ioctl.h> */ /* glibc hides this file sometimes */

static char *version="scsi_serialno.c v. 0.96";

/*  This program originally derived from and inspired by Doug 
 *  Gilbert's scsi_inquiry.c, part of sg_utils package.  
 *  See http://www.torque.net/sg
 *  Copyright (C) 2001 Compaq Computer Corporation 
 *  Copyright (C) 1999 D. Gilbert
 *  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, or (at your option)
 *  any later version.
 */
        
typedef struct my_scsi_ioctl_command {
	unsigned int inlen;  /* _excluding_ scsi command length */
	unsigned int outlen;
	unsigned char data[1];  /* was 0 but that's not ISO C!! */
		/* on input, scsi command starts here then opt. data */
} My_Scsi_Ioctl_Command;

#define OFF (2 * sizeof(unsigned int))

#ifndef SCSI_IOCTL_SEND_COMMAND
#define SCSI_IOCTL_SEND_COMMAND 1
#endif

#define INQUIRY_CMD     0x12
#define INQUIRY_CMDLEN  6
#define INQUIRY_REPLY_LEN 96

#define UNKNOWN_TYPE -1
#define SCSI_TYPE 0
#define CCISS_TYPE 1
#define CPQARRAY_TYPE 2

int
stat_device(int fd, unsigned int *rdev, int *devtype)
{
	struct stat statbuf;
	int res, to;

	*devtype = UNKNOWN_TYPE;
	res = fstat(fd, &statbuf);
	if (res != 0) {
		fprintf(stderr, "Cannot stat, %s\n", strerror(errno));
		return -1;
	}

	*rdev = statbuf.st_rdev;

	if (! S_ISBLK(statbuf.st_mode) ) {
		fprintf(stderr, "Not a block device!\n");
		return -1;
	}
	/* printf("st_rdev = 0x%08x\n", statbuf.st_rdev); */

	switch ( major(statbuf.st_rdev) )
	{
		case COMPAQ_SMART2_MAJOR:
		case COMPAQ_SMART2_MAJOR1:
		case COMPAQ_SMART2_MAJOR2:
		case COMPAQ_SMART2_MAJOR3:
		case COMPAQ_SMART2_MAJOR4:
		case COMPAQ_SMART2_MAJOR5:
		case COMPAQ_SMART2_MAJOR6:
		case COMPAQ_SMART2_MAJOR7:
			*devtype = CPQARRAY_TYPE;
			break;
		case COMPAQ_CISS_MAJOR:
		case COMPAQ_CISS_MAJOR1:
		case COMPAQ_CISS_MAJOR2:
		case COMPAQ_CISS_MAJOR3:
		case COMPAQ_CISS_MAJOR4:
		case COMPAQ_CISS_MAJOR5:
		case COMPAQ_CISS_MAJOR6:
		case COMPAQ_CISS_MAJOR7:
			*devtype = CCISS_TYPE;
			break;
		default: break;
	}

    /* Don't worry, being very careful not to write to a none-scsi file ... */
    if (*devtype == UNKNOWN_TYPE) {
    	res = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &to);
    	if (res < 0) {
        	/* perror("ioctl on scsi device, error"); */
        	return -1;
    	}
	else
	*devtype = SCSI_TYPE;
    }

    return 0;
}

int
generic_cciss_inquiry(int fd, unsigned char evpd, unsigned char page, 
		unsigned char * buf, unsigned char buflen)
{
	int rc;
	unsigned int lun;
	IOCTL_Command_struct iocmd;

	rc = ioctl(fd, CCISS_GETLUNID, &lun); 
	if (rc != 0) return -1;	

	memset(&iocmd, 0, sizeof(iocmd));
        iocmd.LUN_info.LogDev.VolId = lun & 0x3FFFFFFF;
        iocmd.LUN_info.LogDev.Mode = 0x01; /* logical volume addressing */
        iocmd.Request.CDBLen = 6;  /* need to try just 2 bytes here */
        iocmd.Request.Type.Type =  TYPE_CMD; // It is a command.
        iocmd.Request.Type.Attribute = ATTR_SIMPLE;
        iocmd.Request.Type.Direction = XFER_READ; // READ 
        iocmd.Request.Timeout = 0; // Don't time out

	iocmd.Request.CDB[0] = 0x12;
	iocmd.Request.CDB[1] = evpd;

	iocmd.Request.CDB[2] = page;
	iocmd.Request.CDB[3] = 0;
	iocmd.Request.CDB[4] = buflen;
	iocmd.Request.CDB[5] = 0;
	iocmd.buf_size=buflen;
	iocmd.buf = (char *) buf;

        rc = ioctl(fd, CCISS_PASSTHRU, &iocmd);
        if (rc != 0) {
		fprintf(stderr, "cciss inquiry failed: %s\n", 
			strerror(errno));
		return -1;
	}
	return 0;
}

int 
do_cciss_inquiry(int fd, int rdev)
{
	int rc,i;
	unsigned char std_inq[256];
	unsigned char buf[256];
	unsigned char bufsize = 255;

	rc = generic_cciss_inquiry(fd, 0, 0, std_inq, bufsize);
	if (rc != 0) return rc;

	rc = generic_cciss_inquiry(fd, 1, 0x83, buf, bufsize);
	if (rc != 0) return rc;

	printf("%d:    ", minor(rdev));
	for (i=8;i<36;i++) printf("%c", std_inq[i]);
	printf(" ");
	for (i=0;i<16;i++) printf("%02x", buf[i]);
	printf("\n");
	return 0;
}

int
do_cpqarray_inquiry(int fd, int rdev)
{
	return 0;
}

int
generic_scsi_inquiry(int fd, unsigned char evpd, unsigned char page,
	unsigned char *buf, unsigned char  buflen) 
{
	unsigned char inqCmdBlk[INQUIRY_CMDLEN] = 
		{INQUIRY_CMD, evpd, page, 0, INQUIRY_REPLY_LEN, 0};
	int rc, len;
	unsigned char *inqbuf;
	unsigned char *buffp;
	My_Scsi_Ioctl_Command * ishp;

	inqbuf = malloc(OFF + sizeof(inqCmdBlk) + 512);
	memset(inqbuf, 0, OFF + sizeof(inqCmdBlk) + 512); 
	buffp = inqbuf + OFF;

	ishp = (My_Scsi_Ioctl_Command *) inqbuf;
	ishp->inlen = 0;
	ishp->outlen = INQUIRY_REPLY_LEN;
	memcpy(buffp, inqCmdBlk, INQUIRY_CMDLEN);
	rc = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, inqbuf);
	if (rc != 0) {
		free(inqbuf);
		return rc;
	}
	len = INQUIRY_REPLY_LEN < buflen ?  INQUIRY_REPLY_LEN : buflen;
	memcpy(buf, buffp, len); 
	free(inqbuf);
	return len;
}

int
do_scsi_inquiry(int fd, int rdev)
{
	int len, i, found80;
	unsigned char std_inq[256];
	unsigned char buf[256];
	unsigned char *npages;
	unsigned char *pagelist;
	unsigned char *clv = "COMPAQ  LOGICAL VOLUME";

	len = generic_scsi_inquiry(fd, 0, 0, std_inq, 255);
	if (len < 0) return len;

	/* If it's a COMPAQ LOGICAL VOLUME, then we know we can get page 80 
 	   There's a bug in some RA4100 firmware that it tells us it 
	   doesn't support page 0x80, but really it does. */

	if (strncmp(std_inq+8, clv, strlen(clv)) == 0) {
		found80 = 1;
	} else {
		len = generic_scsi_inquiry(fd, 1, 0, buf, 255);
		if (len < 0) return len;
		npages = buf + 3;
		pagelist = buf + 4;
		found80=0;
		for (i=0;i<*npages;i++)
			if (pagelist[i] == 0x80) found80 = 1;
	}

	if (found80) {
		len = generic_scsi_inquiry(fd, 1, 0x80, buf, 255);
		if (len < 0) return len;
		printf("%d:    ", minor(rdev));
		for (i=8;i<36;i++)
			printf("%c", std_inq[i]);
		printf(" ");
		for (i=0;i<16;i++)
			printf("%02x", buf[i]);
		printf("\n");
		return 0;
	} else return -1; 
}


int 
main(int argc, char *argv[])
{
	int fd, rc, k, devtype;
	unsigned int rdev;
	char * file_name = 0;

	if (argc >= 2 && strcmp(argv[1] , "-v")==0) {
		fprintf(stderr, "%s\n", version);
		exit (0);
	}
	for (k = 1; k < argc; ++k) {
		if (*argv[k] != '-')
			file_name = argv[k];
	}
	if (0 == file_name) {
		printf("Usage: '%s <device>'\n", argv[0]);
		printf("    For example: %s /dev/sda\n", argv[0]);
		exit(1);
	}

	fd = open(file_name, O_RDWR);
	if (fd < 0) {
		if ((EROFS == errno) || (EACCES == errno)) {
			fd = open(file_name, O_RDONLY);
			if (fd < 0) {
				fprintf(stderr, "%s: cannot open %s: %s\n",
					argv[0], file_name, strerror(errno));
				exit(1);
			}
		} else {
			fprintf(stderr, "%s: cannot open %s: %s\n",
				argv[0], file_name, strerror(errno));
			exit(1);
		}
	}

	stat_device(fd, &rdev, &devtype); // ignoring return code is ok
					// switch will do the right thing.
	switch(devtype)
	{
		case SCSI_TYPE:
			if (do_scsi_inquiry(fd, rdev) != 0)
				exit(1);
			break;
		case CCISS_TYPE:
			if (do_cciss_inquiry(fd, rdev) != 0)
				exit(1);
			break;
		case CPQARRAY_TYPE:
			if (do_cpqarray_inquiry(fd, rdev) != 0)
				exit(1);
			fprintf(stderr, "%s: cpqarray not yet supported\n", 
				argv[0]);
			break;
		default:
			fprintf(stderr, "%s: unsupported device type.\n", 
				argv[0]);
			break;
	}

	rc = close(fd);
	if (rc < 0) {
		perror("scsi_inquiry: close error");
		return 1;
	}
	return 0;
}
