/*
 * Copyright (C) 2002  Emmanuel VARAGNAT <hddtemp@guzu.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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

// Include file generated by ./configure
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// Gettext includes
#if ENABLE_NLS
#include <libintl.h>
#define _(String) gettext (String)
#else
#define _(String) (String)
#endif

// Standard includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <scsi/scsi.h>
#include <scsi/sg.h>
#include <scsi/scsi_ioctl.h>

// Application specific includes
#include "scsicmds.h"

static void scsi_fixstring(unsigned char *s, int bytecount)
{
  unsigned char *p;
  unsigned char *end;

  p = s;
  end = s + bytecount;

  /* strip leading blanks */
  while (s != end && *s == ' ')
    ++s;
  /* compress internal blanks and strip trailing blanks */
  while (s != end && *s) {
    if (*s++ != ' ' || (s != end && *s && *s != ' '))
      *p++ = *(s-1);
  }
  /* wipe out trailing garbage */
  while (p != end)
    *p++ = '\0';
}

int scsi_SG_IO(int device, unsigned char *cdb, int cdb_len, unsigned char *buffer, int buffer_len, unsigned char *sense, unsigned char sense_len, int dxfer_direction) {
  struct sg_io_hdr io_hdr;

  memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
  io_hdr.interface_id = 'S';
  io_hdr.cmdp = cdb;
  io_hdr.cmd_len = cdb_len;
  io_hdr.dxfer_len = buffer_len;
  io_hdr.dxferp = buffer;
  io_hdr.mx_sb_len = sense_len;
  io_hdr.sbp = sense;
  io_hdr.dxfer_direction = dxfer_direction;
  io_hdr.timeout = 3000; /* 3 seconds should be ample */

  return ioctl(device, SG_IO, &io_hdr);
}

int scsi_SEND_COMMAND(int device, unsigned char *cdb, int cdb_len, unsigned char *buffer, int buffer_len, int dxfer_direction)
{
  unsigned char buf[2048];
  unsigned int inbufsize, outbufsize, ret;

  switch(dxfer_direction) {
    case SG_DXFER_FROM_DEV: 
      inbufsize = 0;
      outbufsize = buffer_len;
      break;
    case SG_DXFER_TO_DEV:
      inbufsize = buffer_len;
      outbufsize = 0;
      break;
    default:
      inbufsize = 0;
      outbufsize = 0;
      break;
  }
  memcpy(buf, &inbufsize , sizeof(inbufsize));
  memcpy(buf + sizeof(inbufsize), &outbufsize , sizeof(outbufsize));
  memcpy(buf + sizeof(inbufsize) + sizeof(outbufsize), cdb, cdb_len);
  memcpy(buf + sizeof(inbufsize) + sizeof(outbufsize) + cdb_len, buffer, buffer_len);

  ret = ioctl(device, SCSI_IOCTL_SEND_COMMAND, buf);
  
  memcpy(buffer, buf + sizeof(inbufsize) + sizeof(outbufsize), buffer_len);
   
  return ret;
}

int scsi_command(int device, unsigned char *cdb, int cdb_len, unsigned char *buffer, int buffer_len, int dxfer_direction)
{
  static int SG_IO_supported = -1;
  int ret;

  if (SG_IO_supported == 1)
    return scsi_SG_IO(device, cdb, cdb_len, buffer, buffer_len, NULL, 0, dxfer_direction);
  else if (SG_IO_supported == 0)
    return scsi_SEND_COMMAND(device, cdb, cdb_len, buffer, buffer_len, dxfer_direction);
  else {
    ret = scsi_SG_IO(device, cdb, cdb_len, buffer, buffer_len, NULL, 0, dxfer_direction);
    if (ret == 0) {
      SG_IO_supported = 1;
      return ret;
    } else {
      SG_IO_supported = 0;
      return scsi_SEND_COMMAND(device, cdb, cdb_len, buffer, buffer_len, dxfer_direction);
    }
  }	 
}

int scsi_inquiry(int device, unsigned char *buffer)
{
  unsigned char cdb[6];
 
  memset(cdb, 0, sizeof(cdb));
  cdb[0] = INQUIRY;
  cdb[4] = 36;  /* should be 36 for unsafe devices (like USB mass storage stuff)
                 *      otherwise they can lock up! SPC sections 7.4 and 8.6 */

  if (scsi_command(device, cdb, sizeof(cdb), buffer, cdb[4], SG_DXFER_FROM_DEV) != 0)
    return 1;
  else {
    scsi_fixstring(buffer + 8, 24);
    buffer[32] = 0;
    return 0;
  }
}

int scsi_modesense(int device, unsigned char pagenum, unsigned char *buffer, int buffer_len) {
  unsigned char cdb[6];
  int ret;
  
  memset(cdb, 0, sizeof(cdb));
  cdb[0] = MODE_SENSE;
  cdb[2] = pagenum;
  cdb[4] = 0xff;

  ret = scsi_command(device, cdb, sizeof(cdb), buffer, buffer_len, SG_DXFER_FROM_DEV);
  if (ret == 0) {
    if ((buffer[3] + 5) > buffer[0]) /* response length too short */
      return -1;
  }
  return ret;
}

int scsi_modeselect(int device, char *buffer) {
  unsigned char cdb[6];
  
  memset(cdb, 0, sizeof(cdb));
  cdb[0] = MODE_SELECT;
  cdb[1] = 0x11;
  cdb[4] = buffer[0] + 1;
  
  memset(buffer, 0, 12);
  buffer[3]  = 0x08;
  buffer[10] = 0x02;
  buffer[12] &= 0x3f;
	  
  return scsi_command(device, cdb, sizeof(cdb), buffer, cdb[4], SG_DXFER_TO_DEV);
}

int scsi_logsense(int device, int pagenum, unsigned char *buffer, int buffer_len) {
  unsigned char cdb[10];
  
  memset(cdb, 0, sizeof(cdb));
  cdb[0] = LOG_SENSE;
  cdb[2] = 0x40 | pagenum;
  cdb[7] = 0x04;
  
  return scsi_command(device, cdb, sizeof(cdb), buffer, buffer_len, SG_DXFER_FROM_DEV);
}

int scsi_smartsupport(int device) {
  unsigned char buf[255];
	
  if (scsi_modesense (device, EXCEPTIONS_CONTROL_PAGE, buf, sizeof(buf)) != 0)
    return 0;
  else
    return (buf[14] & 0x08) == 0;
}

int scsi_smartDEXCPTdisable(int device) {
  unsigned char buf[255];

  if (scsi_modesense (device, EXCEPTIONS_CONTROL_PAGE, buf, sizeof(buf)) != 0)
    return 1;

  if (buf[14] & 0x08) {
    buf[14] &= 0xf7;
    buf[15] = 0x04;
    return scsi_modeselect (device, buf);
  }
  else
    return 0;
}

