/*
 * scsimodepage.c - SCSI MODE SENSE/SELECT utility.
 *
 * Author: John DiMarco, University of Toronto, Computer Science Laboratory
 *         jdd@cs.toronto.edu
 */

#ifndef lint
static char rcsid[] = "$Id: scsimodepage.c,v 1.5 1998/03/27 16:44:39 jdd Exp $";
#endif

#include <stdio.h>
#ifdef FILENAME_MAX /* defined in stdio.h only for SunOS >=5.x */
#define SYSV
#endif /* FILENAME_MAX */
#include <stdlib.h>
#include <unistd.h>
#include <values.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <fcntl.h>
#ifdef SYSV
#include <string.h>
#include <sys/scsi/impl/types.h>
#include <sys/scsi/impl/uscsi.h>
#else /* !SYSV */
#include <strings.h>
#include <scsi/impl/types.h>
#include <scsi/impl/uscsi.h>
#endif /* SYSV */

#include "utils.h"

char *progname;
int d=0, modesense=FALSE, modeselect=FALSE, save=FALSE;

#define HEX 16
#define GROUP0_PAGESIZE 2550
#define GROUP0_CMDSIZE 10
#define GROUP0_RQSIZE 20
#define GROUP0_MODE_SENSE 0x1a
#define LOG_SENSE_CMD                   0x4d
#define GROUP0_MODE_SELECT 0x15

/* 
 * I prefer the output to have 12 hex bytes per line, because that's the
 * mode header plus one parameter list (most common case), making the 
 * actual page contents most commonly start at the second line. 
 */
#ifndef OUTPUT_NUM_LINE
#define OUTPUT_NUM_LINE 12	/* number of hex bytes per line on output */
#endif /* OUTPUT_NUM_LINE */
#ifndef ERROR_NUM_LINE 
#define ERROR_NUM_LINE 20	/* number of hex bytes per line on error */
#endif /* ERROR_NUM_LINE */

static void output();

void usage()
{
	(void)fprintf(stderr, "Usage: %s [-gps] device [mode page]\n", 
			progname);
	(void)exit(2);
}

/*
 * main - parse arguments and handle options
 */
int main(argc, argv)
int argc;
char *argv[];
{
	int c, fd;
	int errflg = 0;
	extern int optind;
	extern char *optarg;
	struct uscsi_cmd ucmd;
	u_char cdb[GROUP0_CMDSIZE];
	u_char rq[GROUP0_RQSIZE];
	unsigned char pagebuf[GROUP0_PAGESIZE];
	u_char modepage; 
	char *device;

	progname = argv[0];
	while ((c = getopt(argc, argv, "dgps")) != EOF)
		switch (c) {
		case 'd':
			++d;
			break;
		case 'g': /* get mode page or pages (MODE SENSE) */
			modesense=TRUE;
			break;
		default:
			errflg++;
			break;
		}
	if (errflg) {
		usage();
	}

	/* 
	 * The default option is -g (MODE SENSE), so if we haven't seen a 
	 * -g or -s, assume -g. 
	 */
	if(!modesense && !modeselect) modesense=TRUE;

	if (optind >= argc){
		/* no arguments -- this is wrong */
		usage();
	} else {
		device=argv[optind++];
		if(argc==optind){
			/* no second argument -- use default mode page */
			modepage=(u_char)0x3f; /* All current pages */
		} else {
			modepage=(u_char)strtol(argv[optind++],NULL,HEX);
			if(argc > optind){
				/* too many arguments -- give usage message */
				usage();
			}
		}
	}

	/* 
	 * Open the SCSI device.  In this case, the O_NDELAY is a magic 
	 * flag to the OS to tell it not to worry about the device being
	 * labelled, etc.  This is the convention that format uses to
	 * tell the OS to permit it to access unlabelled disks.  We'll do
	 * the same thing so that we can do mode sense/select on unlabelled
	 * disks. 
	 */

	if(0>(fd=open(device, (modeselect?O_RDWR:O_RDONLY)|O_NDELAY))){
		perror(device);
		exit(1);
	}

	if(modesense){
		int p;

		/* set up cdb */
		(void)memset(cdb, 0, sizeof(cdb));
		cdb[0]=LOG_SENSE_CMD;
		cdb[2]=modepage;	   /* Mode Page */
printf("log page is 0x%x\n", modepage);
		cdb[7]=(GROUP0_PAGESIZE)>>8;
		cdb[8]=(GROUP0_PAGESIZE)&0xff;    /* Allocation length */

		/* set up USCSI command structure */
		(void)memset(rq, 0, sizeof(rq));
		ucmd.uscsi_cdb = (caddr_t)cdb;
		ucmd.uscsi_cdblen = sizeof(cdb);
		ucmd.uscsi_bufaddr = (caddr_t)pagebuf;
		ucmd.uscsi_buflen = sizeof(pagebuf);
#if 0
		ucmd.uscsi_flags = USCSI_DIAGNOSE | USCSI_ISOLATE | USCSI_READ;
#else
		ucmd.uscsi_flags = USCSI_SILENT | USCSI_ISOLATE | USCSI_READ;
#endif
#ifdef USCSI_RQENABLE
		ucmd.uscsi_rqbuf = (caddr_t)rq;
		ucmd.uscsi_rqlen = sizeof(rq);
		ucmd.uscsi_flags |= USCSI_RQENABLE;
#endif /* USCSI_RQENABLE */

		if(0>ioctl(fd, USCSICMD, &ucmd)){
			if(EIO==errno){
				(void)fprintf(stderr, "LOG SENSE failed.\n");
#ifdef USCSI_RQENABLE
				(void)
				output(stderr, rq, sizeof(rq), 
					ERROR_NUM_LINE);
#endif /* USCSI_RQENABLE */
			} else {
				perror("mode sense");
			}
			exit(1);
		}
		printf("uscsi_status status: 0x%0x\n", ucmd.uscsi_status);

		output(stdout, (u_char *)pagebuf, pagebuf[3]+4, 
			OUTPUT_NUM_LINE);

		printf("\nPage:\t0x%0x\n", pagebuf[0]);
		printf("Len-3:\t%u\n", pagebuf[3]);
		for (p=4; p < pagebuf[3]+4; ) {
		  unsigned int v0, v1;

                  printf("Param %0x, Len %d, Value ",
                    (pagebuf[p]<<8) + pagebuf[p+1], pagebuf[p+3]);

                      v0 = (pagebuf[p+4] << 24) |
                           (pagebuf[p+5] << 16) |
                           (pagebuf[p+6] <<  8) |
                           (pagebuf[p+7]);

		    if (pagebuf[p+3] == 8) {
                      v1 = (pagebuf[p+8] << 24) |
                           (pagebuf[p+9] << 16) |
                           (pagebuf[p+10] << 8) |
                           (pagebuf[p+11]);
		      printf("0x%08x.%08x (%d.%d)\n", v0, v1, v0, v1);
		    } else if (pagebuf[p+3] == 4) {
		      printf("0x%08x (%u)\n", v0, v0);
		    } else {
		      fprintf(stderr, "funny length %d\n", pagebuf[p+3]);
		      exit(1);
		    }
		  p += 4 + pagebuf[p+3];
	        }
	}
	(void)close(fd);
			
	return(0);
}

static void output(fp, array, len, num_line)
FILE *fp;
u_char *array;
int len, num_line;
{
	int i;
	for(i=0;i<len;i++){
                (void)fprintf(fp, "%2.2x%s", (u_int)array[i],
			(i+1-num_line)%num_line?" ":"\n");
        }
        if(i%num_line) (void)fprintf (fp, "\n");
}
