/*
 * This library works on the Linux sg device driver
 *
 * $Id: scsi_iface.c,v 1.4 2001/02/16 00:29:33 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 <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>  /* ioctl */

#define __USE_XOPEN
#define __USE_GNU
#include <stdlib.h>

#include "scsi_iface.h"

/* start our scsi command (need to know output size) */
int start_scsi_cmd (
			int fd,			/* sg fd */
			unsigned cmd_len,	/* command length */
		 	unsigned in_size,	/* input data size */
		 	unsigned char *i_buff,	/* input buffer */
		 	unsigned out_size	/* output data size */
     		      ) {
		
  int status = 0;
  struct sg_header *sg_hd;

  /* safety checks */
  if (!cmd_len) {
    fprintf(stderr,"start_scsi_cmd: cmd_len == 0\n");
    return -1;			/* need a cmd_len != 0 */
  }
  if (!i_buff) {
    fprintf(stderr,"start_scsi_cmd: i_buff == NULL\n");
    return -1;			/* need an input buffer != NULL */
  }

  /* keep the packet size under 4k */
  if (SG_LEN + cmd_len + in_size > MAX_PACKET) {
    fprintf(stderr,"packet too large\n");
    return -1;
  }

  /* generic scsi device header construction */
  sg_hd = (struct sg_header *) i_buff;
  /* useful stuff */
  sg_hd->reply_len = SG_LEN + out_size;
  sg_hd->twelve_byte = (cmd_len == 12); /* should be set */
  /* not really used stuff */
  sg_hd->other_flags = 0;	/* not used */
  sg_hd->result = 0;
  sg_hd->pack_id = 0;
  /* ignored stuff */
#if     0
  sg_hd->pack_len = SG_LEN + cmd_len + in_size;	/* not necessary */
#endif

  /* send command */
  /* FIXME: handle EAGAIN errno */
  status = write (fd, i_buff, SG_LEN + cmd_len + in_size);
  if (status < 0 || status != SG_LEN + cmd_len + in_size) {
      /* some error happened */
      fprintf (stderr, "write(sg fd %d) result = 0x%x cmd = 0x%x: ",
	       fd, sg_hd->result, i_buff[SG_LEN]);
      perror ("");
      return status;
  }

  return 0;
}

/* get results from last scsi cmd (need output buffer and size) */
int finish_scsi_cmd (
			int fd,			/* sg fd */
		 	unsigned out_size,	/* output data size */
		 	unsigned char *o_buff	/* output buffer */
		     ) {
  int status = 0;
  struct sg_header *sg_hd;

  /* safety checks */
  if (SG_LEN + out_size > MAX_PACKET) {
    fprintf(stderr,"finish_scsi_cmd: packet too large\n");
    return -1;
  }
  if (!o_buff) {
    fprintf(stderr,"finish_scsi_cmd: o_buff == NULL\n");
    return -1;
  }
  
  /* retrieve result */
  /* FIXME: handle EINTR errno */
  status = read (fd, o_buff, SG_LEN + out_size);
  if (status < 0 || status != SG_LEN + out_size) {
      /* some error happened */
      fprintf (stderr, "read(sg fd %d) result = 0x%x cmd = 0x%x\n",
	       fd, sg_hd->result, o_buff[SG_LEN]);
      fprintf (stderr, "read(sg fd %d) sense "
	       "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", fd,
	       sg_hd->sense_buffer[0], sg_hd->sense_buffer[1],
	       sg_hd->sense_buffer[2], sg_hd->sense_buffer[3],
	       sg_hd->sense_buffer[4], sg_hd->sense_buffer[5],
	       sg_hd->sense_buffer[6], sg_hd->sense_buffer[7],
	       sg_hd->sense_buffer[8], sg_hd->sense_buffer[9],
	       sg_hd->sense_buffer[10], sg_hd->sense_buffer[11],
	       sg_hd->sense_buffer[12], sg_hd->sense_buffer[13],
	       sg_hd->sense_buffer[14], sg_hd->sense_buffer[15]);
      if (status < 0)
	perror ("");
  }

  /* Look if we got what we expected to get */
  if (status == SG_LEN + out_size)
    status = 0;			/* got them all */

  return status;		/* 0 means no error */
}

/* process a complete scsi cmd. Use the generic scsi interface. */
int waitfor_scsi_cmd (
			int fd,			/* sg fd */
			unsigned cmd_len,	/* command length */
		 	unsigned in_size,	/* input data size */
		 	unsigned char *i_buff,	/* input buffer */
		 	unsigned out_size,	/* output data size */
		 	unsigned char *o_buff	/* output buffer */
     		      ) {

  int result;
  static char buf[MAX_PACKET+SG_LEN];

  if ((result=start_scsi_cmd(fd, cmd_len, in_size, i_buff, out_size))!=0) {
	fprintf(stderr,"start_scsi_cmd failed\n");
	return result;
  }

  if (!o_buff) {
	memset(buf,0,sizeof(buf));
	o_buff=buf;
  }

  if ((result=finish_scsi_cmd(fd, out_size, o_buff))!=0) {
	fprintf(stderr,"finish_scsi_cmd failed\n");
	return result;
  }

  return 0;
}

