/*
 * This library implements a cleaner access system to the sg device
 *
 * $Id: scsi_tools.c,v 1.14 2001/02/16 14:52:52 root Exp $
 *
 * Copyright (C) 2001 Kees Cook
 * cook@cpoint.net, http://outflux.net/
 *
 * 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
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include "scsi_tools.h"
#include "scsi_iface.h"

#if 0
void printchars(u_char * str, int len) {
	int i;

	for (i=0;i<len;i++) {
		printf("%d: %c (0x%02X)\n",i, str[i],str[i]);
	}
}
#endif

/* request vendor brand and model */
scsi_info * scsi_inquiry (scsi_info * dev) {
  static u_char cmd[SG_LEN + 18]; /* SCSI command buffer */
  static u_char Inqbuffer[SG_LEN + INQUIRY_REPLY_LEN];
  u_char cmdblk[INQUIRY_CMDLEN] = { INQUIRY_CMD,	/* command */
    0,				/* lun/reserved */
    0,				/* page code */
    0,				/* reserved */
    INQUIRY_REPLY_LEN,		/* allocation length */
    0
  };				/* reserved/flag/link */

  if (!dev) {
	fprintf(stderr,"scsi_inquery got NULL dev?!\n");
	return NULL;
  }

  memcpy (cmd + SG_LEN, cmdblk, sizeof (cmdblk));

  /*
   * +------------------+
   * | struct sg_header | <- cmd
   * +------------------+
   * | copy of cmdblk   | <- cmd + SG_LEN
   * +------------------+
   */

  /* clear out inq buffer */
  memset(Inqbuffer,0,sizeof(Inqbuffer));

  if (waitfor_scsi_cmd (dev->fd, sizeof (cmdblk), 0, cmd,
		       INQUIRY_REPLY_LEN, Inqbuffer)) {
      fprintf (stderr, "scsi_inquiry failed\n");
      return NULL;
  }

  /* gather device characteristics */
  if (ioctl(dev->fd, SG_GET_SCSI_ID, &dev->sginfo)<0) {
	perror("scsi_inquiry: SG_GET_SCSI_ID");
	return NULL;
  }

  /* extract null-terminated text from inquery command */
  strncpy(dev->vendor,Inqbuffer+SG_LEN+VENDOR_START,8);
  dev->vendor[8]='\0';
  strncpy(dev->product,Inqbuffer+SG_LEN+VENDOR_START+8,16);
  dev->product[16]='\0';
  strncpy(dev->version,Inqbuffer+SG_LEN+VENDOR_START+24,4);
  dev->version[4]='\0';

  /* save the full inq buffer for vendor-specific stuff */
  if (!(dev->fullinq=(char*)malloc(INQUIRY_REPLY_LEN+1))) {
	perror("scsi_inquiry: malloc");
	return NULL;
  }
  memcpy(dev->fullinq,Inqbuffer+SG_LEN,INQUIRY_REPLY_LEN);
  dev->fullinq[INQUIRY_REPLY_LEN]='\0';

  return dev;
}

int scsi_unitready (scsi_info * dev) {
  static u_char cmd[SG_LEN + 18]; /* SCSI command buffer */
  /* request READY status */
  static u_char cmdblk[TESTUNITREADY_CMDLEN] = {
    TESTUNITREADY_CMD,		/* command */
    0,				/* lun/reserved */
    0,				/* reserved */
    0,				/* reserved */
    0,				/* reserved */
    0
  };				/* reserved */

  if (!dev) {
	fprintf(stderr,"scsi_unitready got NULL dev?!\n");
	return 0;
  }

  memcpy (cmd + SG_LEN, cmdblk, sizeof (cmdblk));

  /*
   * +------------------+
   * | struct sg_header | <- cmd
   * +------------------+
   * | copy of cmdblk   | <- cmd + SG_LEN
   * +------------------+
   */

  if (waitfor_scsi_cmd (dev->fd, sizeof (cmdblk), 0, cmd, 0, NULL)) {
      fprintf (stderr, "scsi_testunitready failed\n");
      return 0; /* not ready */
  }

  return
    *(((struct sg_header *) cmd)->sense_buffer + ADD_SENSECODE) !=
    NO_MEDIA_SC ||
    *(((struct sg_header *) cmd)->sense_buffer + ADD_SC_QUALIFIER) !=
    NO_MEDIA_SCQ;
}

scsi_info * scsi_open_name(char * filename) {
	scsi_info * dev;
	int flags;

	if (!(dev=(scsi_info*)calloc(1,sizeof(scsi_info)))) {
		perror("scsi_open malloc");
		return NULL;
	}
	if (!filename) {
		fprintf(stderr,"scsi_open got NULL filename?!\n");
		return NULL;
	}

	if ((dev->fd = open (filename, O_RDWR | O_EXCL | O_NONBLOCK))<0) {
		if (errno != ENOENT && errno != ENXIO) {
			fprintf(stderr,"scsi_open('%s'): %s\n",filename,
				strerror(errno));
		}
		free(dev);
		return NULL;
	}
	if ((flags = fcntl(dev->fd, F_GETFL))<0) {
		perror("scsi_open: fcntl(F_GETFL)");
		goto failed;
	}
	if (fcntl(dev->fd, F_SETFL, flags & (~ O_NONBLOCK))<0) {
		perror("scsi_open: fcntl(F_SETFL)");
		goto failed;
	}

	/* perform inquiry */
	if (!scsi_inquiry(dev)) {
		fprintf(stderr,"scsi_open: failed inquiry\n");
		goto failed;
	}

	dev->filename=strdup(filename);

	return dev;
failed:
	scsi_close(dev);
	return NULL;
}

scsi_info * scsi_open_id(int host, int chan, int id, int lun) {
  scsi_info * dev;
  char sgfile[24];
  int i;

  for (i=0;i<128;i++) {
        sprintf(sgfile,"/dev/sg%d",i);

        if ((dev = scsi_open_name(sgfile))!=NULL) {
		if (	dev->sginfo.host_no==host &&
			dev->sginfo.channel==chan &&
			dev->sginfo.scsi_id==id &&
			dev->sginfo.lun==lun) return dev;
                scsi_close(dev);
        }
  }
  return NULL;
}

void scsi_close(scsi_info * dev) {
  if (!dev) {
	fprintf(stderr,"scsi_close got NULL dev?!\n");
	return;
  }
  close(dev->fd);
  if (dev->filename) free(dev->filename);
  if (dev->fullinq) free(dev->fullinq);
  free(dev);
}

/* max str length is 13 */
char * scsi_type(int type) {
	switch (type) {
	case TYPE_DISK: return "Disk";
	case TYPE_TAPE: return "Tape";
	case TYPE_PROCESSOR: return "Processor";
	case TYPE_WORM: return "Worm";
	case TYPE_ROM: return "CD-Rom";
	case TYPE_SCANNER: return "Scanner";
	case TYPE_MOD: return "Mag-optical";
	case TYPE_MEDIUM_CHANGER: return "Changer";
	case TYPE_COMM: return "Communication";
	case TYPE_ENCLOSURE: return "Enclosure";
	case TYPE_NO_LUN: return "No Lun";
	default: return "Unknown";
	}
}
