/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2006
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-disk.c :: Midnight Code low-level disk library	*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/hdreg.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>

#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-config.h"
#include "midnightcode-disk.h"


int
midnightcode_disk_flush(struct midnightcode_disk_element_t * disk) {
  int fd;


  if(!disk)
    return 1;

  if(access(disk->device, F_OK) != 0)
    return 1;

  if((fd = open(disk->device, O_NONBLOCK | O_RDONLY)) < 0)
    return 1;

  if(ioctl(fd, BLKFLSBUF) < 0) {
    close(fd);
    return 1;
  }

  close(fd);

  return 0;
}


int
midnightcode_disk_sg_query(int fd, void * result, int result_len,
                           int opt1, int opt2) {
  unsigned char query[6] = {0x12, 0, 0, 0, 0, 0};
  char * result_ptr;
  struct sg_io_hdr sg_io;
  unsigned char sb[128];


  if(result != NULL && result_len > 0) {
    result_ptr = result;
    memset(result, 0, result_len);
    result_ptr[0] = 0x7f;
  }
  memset(&sg_io, 0, sizeof(struct sg_io_hdr));
  memset(sb, 0, sizeof(sb));

  query[1] |= opt1;
  query[2] = (unsigned char)opt2;
  query[3] = (unsigned char)((result_len >> 8) & 0xff);
  query[4] = (unsigned char)(result_len & 0xff);

  sg_io.interface_id = 'S';
  sg_io.dxfer_direction = SG_DXFER_FROM_DEV;
  sg_io.dxferp = result;
  sg_io.dxfer_len = result_len;
  sg_io.cmdp = query;
  sg_io.cmd_len = sizeof(query);
  sg_io.sbp = sb;
  sg_io.mx_sb_len = sizeof(sb);
  sg_io.timeout = 1500;

  if (ioctl(fd, SG_IO, &sg_io) < 0)
      return -1;

  if((sg_io.dxfer_len - sg_io.resid) < 4)
    return 1;

  return 0;

}


int
midnightcode_disk_probe_scsi(int fd, 
                             struct midnightcode_disk_element_t * disk) {
  static unsigned char result[37];
  int result_len;
  int device_type;
  unsigned char temp_buf[256];
  int temp_len, temp_max, temp_offset;


  temp_len = sizeof(temp_buf);

  /* TODO: Consider the implications of a Disk default type - IDE Tapes? */
  device_type = 0;
  disk->device_type = device_type;
  snprintf(disk->device_type_s, 127, "%s",
    midnightcode_disk_device_t[device_type]);

  result_len = 36;
  if(midnightcode_disk_sg_query(fd, result, result_len, 0, 0))
    return 1;

  result_len = result[4] + 5;
  device_type = result[0] & 0x1f;

  if(result_len > 36 && result_len < 256) {
    if(midnightcode_disk_sg_query(fd, result, result_len, 0, 0))
      return 1;

    if(result_len != (result[4] + 5))
      return 1;
  }

  if(device_type < 0 || device_type > 31)
    return 1;

  disk->device_type = device_type;
  memset(disk->device_type_s, 0, 127);
  snprintf(disk->device_type_s, 127, "%s",
    midnightcode_disk_device_t[device_type]);

  memset(temp_buf, 0, temp_len);
  memset(temp_buf, 0xff, 4);
  if(!midnightcode_disk_sg_query(fd, temp_buf, temp_len, 1, 0) &&
     temp_buf[1] == 0 && temp_buf[2] == 0) {
    temp_max = temp_buf[3];
    for(temp_offset = 0; temp_offset < temp_max; temp_offset++) {
      if(temp_buf[temp_offset + 4] == 0x80)
        break;
    }
    if(!midnightcode_disk_sg_query(fd, temp_buf, temp_len, 1, 0x80) &&
      temp_offset < temp_max) {
      temp_max = temp_buf[3];
      if(temp_max > 127)
        temp_max = 127;
      if(temp_buf[1] == 0x80 && temp_max > 0) {
        memcpy(disk->serial_no, temp_buf + 4, temp_max);
        disk->serial_no[temp_max] = '\0';
      }
    }
  }

  if(result_len < 36 && result_len > 8)
    result[result_len] = '\0';

  if(result_len < 9)
    return 2;
  memset(temp_buf, 0, temp_len);
  memcpy(temp_buf, &result[8], 8);
  snprintf(disk->vendor_id, 127, "%s", temp_buf);

  if(result_len < 17)
    return 0;
  memset(temp_buf, 0, temp_len);
  memcpy(temp_buf, &result[16], 16);
  snprintf(disk->product_id, 127, "%s", temp_buf);

  if(result_len < 33)
    return 0;
  memset(temp_buf, 0, temp_len);
  memcpy(temp_buf, &result[32], 4);
  snprintf(disk->version, 127, "%s", temp_buf);

  return 0;

}


int
midnightcode_disk_probe_hdio(int fd,
                             struct midnightcode_disk_element_t * disk) {
  struct hd_driveid hd_id;


  memset(&hd_id, 0, sizeof(struct hd_driveid));

  if(ioctl(fd, HDIO_GET_IDENTITY, &hd_id) < 0)
    return 1;

  memcpy(disk->product_id, hd_id.model, 40);
  memcpy(disk->version, hd_id.fw_rev, 8);
  memcpy(disk->serial_no, hd_id.serial_no, 20);

  if(ioctl(fd, HDIO_GET_DMA, &disk->dma) < 0)
    disk->dma = -1;

  return 0;
}


int
midnightcode_disk_capacity(int fd, struct midnightcode_disk_element_t * disk) {
  int capacity_io;
  char * capacity_ptr;


  /* TODO: Replace static 512byte block size with per-device block size */

  if(ioctl(fd, BLKGETSIZE, &capacity_io) < 0)
    return 1;

  disk->capacity_blocks = (long double)capacity_io;
  disk->capacity_bytes = (long double)((long double)disk->capacity_blocks *
                         (long double)512);
  disk->capacity_value = disk->capacity_bytes;
  capacity_ptr = disk->capacity_units;
  memset(disk->capacity_units, 0, 64);
  midnightcode_parse_size((long double *)&disk->capacity_value,
                           capacity_ptr, 127);
  snprintf(disk->capacity_s, 127, "%.2Lf %s",
           disk->capacity_value, disk->capacity_units);

  return 0;
}


int
midnightcode_disk_probe(struct midnightcode_disk_element_t * disk) {
  int fd;
  int ret_scsi, ret_hdio;


  ret_scsi = 0;
  ret_hdio = 0;

  if(access(disk->device, F_OK) != 0)
    return 1;

  if((fd = open(disk->device, O_NONBLOCK | O_RDONLY)) < 0)
    return 1;

  ret_scsi = midnightcode_disk_probe_scsi(fd, disk);
  if(ret_scsi)
    ret_hdio = midnightcode_disk_probe_hdio(fd, disk);

  if(strlen(disk->product_id))
    midnightcode_parse_trim(disk->product_id, strlen(disk->product_id));
  if(strlen(disk->vendor_id))
    midnightcode_parse_trim(disk->vendor_id, strlen(disk->vendor_id));
  if(strlen(disk->version))
    midnightcode_parse_trim(disk->version, strlen(disk->version));
  if(strlen(disk->serial_no))
    midnightcode_parse_trim(disk->serial_no, strlen(disk->serial_no));


  if(ret_scsi == 1 && ret_hdio)
    return 2;

  if(midnightcode_disk_capacity(fd, disk))
    return 3;

  close(fd);

  return 0;
}


int
midnightcode_disk_list(struct midnightcode_disk_head_t *elements) {
  int devn, count;
  struct midnightcode_disk_device_t * dev;
  struct midnightcode_disk_element_t * now_element;
  struct midnightcode_disk_device_t disk_device[] = {
    { "/dev/hda" }, { "/dev/hdb" }, { "/dev/hdc" }, { "/dev/hdd" },
    { "/dev/hde" }, { "/dev/hdf" }, { "/dev/hdg" }, { "/dev/hdh" },
    { "/dev/hdi" }, { "/dev/hdj" }, { "/dev/hdk" }, { "/dev/hdl" },
    { "/dev/hdm" }, { "/dev/hdn" }, { "/dev/hdo" }, { "/dev/hdp" },
    { "/dev/sda" }, { "/dev/sdb" }, { "/dev/sdc" }, { "/dev/sdd" },
    { "/dev/sde" }, { "/dev/sdf" }, { "/dev/sdg" }, { "/dev/sdh" },
    { "/dev/sdi" }, { "/dev/sdj" }, { "/dev/sdk" }, { "/dev/sdl" },
    { "/dev/sdm" }, { "/dev/sdn" }, { "/dev/sdo" }, { "/dev/sdp" },
    { 0 }
  };
 
 
  count = 0;

  for(dev = disk_device, devn = 0; disk_device[devn].device != NULL; devn++) {
    now_element = malloc(sizeof(struct midnightcode_disk_element_t));
    memset(now_element, 0, sizeof(struct midnightcode_disk_element_t));

    snprintf(now_element->device, 63, "%s", disk_device[devn].device);

    if(midnightcode_disk_probe(now_element)) {
      free(now_element);
    } else {
      midnightcode_list_append(elements, now_element);
      count++;
    }
  }

  return count;
}


int
midnightcode_disk_bytype(struct midnightcode_disk_head_t *elements, int type) {
  struct midnightcode_disk_element_t * now_element;
  struct midnightcode_disk_element_t * old_element;


  if(midnightcode_disk_list(elements) < 0)
    return -1;

  old_element = NULL;
  midnightcode_list_for_each(now_element, elements) {
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
    if(now_element->device_type != type) {
      midnightcode_list_remove(elements, now_element);
      old_element = now_element;
    }
  }
  if(old_element != NULL)
    free(old_element);

  return 0;
}


int
midnightcode_disk_only(struct midnightcode_disk_head_t *elements, int size) {
  struct midnightcode_disk_element_t * now_element;
  struct midnightcode_disk_element_t * old_element;
  long double capacity;


  capacity = (long double)size * (long double)1000000000;

  old_element = NULL;
  midnightcode_list_for_each(now_element, elements) {
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
    if(now_element->capacity_bytes < capacity ||
      now_element->device_type != MIDNIGHTCODE_DISK_TYPE_DISK) {
      midnightcode_list_remove(elements, now_element);
      old_element = now_element;
    }
  }
  if(old_element != NULL)
    free(old_element);

  return 0;
}


int
midnightcode_disk_to_config(
                     struct midnightcode_disk_head_t * disk_elements,
                     struct midnightcode_config_head_t * config_elements) {
  struct midnightcode_disk_element_t * disk_element;


  if(!disk_elements || !config_elements)
    return -1;

  if(midnightcode_config_del_structure(config_elements,
                                       MIDNIGHTCODE_DISK_STRUCTURE))
    return -1;

  midnightcode_list_for_each(disk_element, disk_elements) {
    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "is_enabled",
                                    &disk_element->is_enabled,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "vendor_id",
                                    disk_element->vendor_id,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "product_id",
                                    disk_element->product_id,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "version",
                                    disk_element->version,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "serial_no",
                                    disk_element->serial_no,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "device_type",
                                    &disk_element->device_type,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "capacity_blocks",
                                    &disk_element->capacity_blocks,
                                    MIDNIGHTCODE_PARSE_TYPE_LONGDOUBLE))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "capacity_bytes",
                                    &disk_element->capacity_bytes,
                                    MIDNIGHTCODE_PARSE_TYPE_LONGDOUBLE))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "dma",
                                    &disk_element->dma,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_pv_created",
                                    &disk_element->lvm_pv_created,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_pe_size",
                                    &disk_element->lvm_pe_size,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_vg_member",
                                    &disk_element->lvm_vg_member,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_vg_label",
                                    disk_element->lvm_vg_label,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;


  }

  return 0;
}


int
midnightcode_disk_from_config(
                     struct midnightcode_disk_head_t * disk_elements,
                     struct midnightcode_config_head_t * config_elements) {
  struct midnightcode_disk_element_t * disk_element;
  struct midnightcode_config_section_element_t * section_element;
  struct midnightcode_config_section_element_t * old_element;
  struct midnightcode_config_section_head_t * section_elements;
  void * tmp_void_p;
  char * char_p;
  midnightcode_list_init(config_section_struct_head,
                         midnightcode_config_section_element_t);


  if(!disk_elements || !config_elements)
    return -1;

  tmp_void_p = (void *)&config_section_struct_head;
  section_elements = tmp_void_p;

  /* TODO: free section_elements memory */

  if(!midnightcode_config_get_sections(config_elements,
                                       section_elements,
                                       MIDNIGHTCODE_DISK_STRUCTURE))
    return -1;

  old_element = NULL;
  midnightcode_list_for_each(section_element, section_elements) {
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
    old_element = section_element;

    disk_element = malloc(sizeof(struct midnightcode_disk_element_t));
    memset(disk_element, 0,
      sizeof(struct midnightcode_disk_element_t));

    snprintf(disk_element->device, 63, "%s", section_element->section);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "is_enabled",
                                    &disk_element->is_enabled, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "vendor_id",
                                    disk_element->vendor_id, 128,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "product_id",
                                    disk_element->product_id, 128,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "version",
                                    disk_element->version, 128,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "serial_no",
                                    disk_element->serial_no, 128,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "device_type",
                                    &disk_element->device_type, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    memset(disk_element->device_type_s, 0, 127);
    snprintf(disk_element->device_type_s, 127, "%s",
      midnightcode_disk_device_t[disk_element->device_type]);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "capacity_blocks",
                                    &disk_element->capacity_blocks, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_LONGDOUBLE))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "capacity_bytes",
                                    &disk_element->capacity_bytes, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_LONGDOUBLE))
      return -1;

    disk_element->capacity_value = disk_element->capacity_bytes;

    char_p = disk_element->capacity_units;
    midnightcode_parse_size((long double *)&disk_element->capacity_value,
                            char_p, 127);
    snprintf(disk_element->capacity_s, 127, "%.2Lf %s",
             disk_element->capacity_value, disk_element->capacity_units);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "dma",
                                    &disk_element->dma, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_pv_created",
                                    &disk_element->lvm_pv_created, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_pe_size",
                                    &disk_element->lvm_pe_size, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_vg_member",
                                    &disk_element->lvm_vg_member, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_DISK_STRUCTURE,
                                    disk_element->device,
                                    "lvm_vg_label",
                                    disk_element->lvm_vg_label, 128,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;


    midnightcode_list_append(disk_elements, disk_element);

  }
  if(old_element != NULL)
    free(old_element);

  return 0;
}


