/*

                                   _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>

    Midnight Code is a registered trademark of Ian Latter.

    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 <linux/cdrom.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>

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



char * midnightcode_disk_device_l[32] = {
  "Disk",
  "Tape",
  "Printer",
  "Processor",
  "Optical Disk (Write Once)",
  "Optical Disk (CD/DVD)",
  "Scanner",
  "Optical Memory Device",
  "Medium Changer",
  "Communications Device",
  "Graphics Device (a)",
  "Graphics Device (b)",
  "Storage Array Controller",
  "Enclosure Services Device",
  "Simplified Direct Access Device",
  "Optical Card Reader/Writer Device",
  "Bridge Controller Commands",
  "Object Based Storage Device",
  "Automation/Driver Interface",
  "Unknown Device (0x13)",
  "Unknown Device (0x14)",
  "Unknown Device (0x15)",
  "Unknown Device (0x16)",
  "Unknown Device (0x17)",
  "Unknown Device (0x18)",
  "Unknown Device (0x19)",
  "Unknown Device (0x1a)",
  "Unknown Device (0x1b)",
  "Unknown Device (0x1c)",
  "Unknown Device (0x1d)",
  "Well-known Logical Unit",
  "No physical device on this Logical Unit"
};



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;
  int genuine_scsi;
  char host_type[128];
  int scsi_address[2];


  if(!disk)
    return 1;

  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_l[device_type]);

  if(!ioctl(fd, SG_EMULATED_HOST, &genuine_scsi)) {
    if(genuine_scsi) {
      disk->scsi_emulation = MIDNIGHTCODE_DISK_EMULATION_ENABLED;
    } else {
      disk->scsi_emulation = MIDNIGHTCODE_DISK_EMULATION_DISABLED;
    }
  }

  memset(host_type, 0, sizeof(host_type));
  *(int*)host_type = sizeof(host_type);
  if(ioctl(fd, SCSI_IOCTL_PROBE_HOST, host_type) >= 0) {
    memcpy(disk->bus_type_s, host_type, sizeof(disk->bus_type_s));
    if(strstr(host_type, "USB Mass Storage") != 0) {
      disk->bus_type = MIDNIGHTCODE_DISK_BUS_USB;
    } else {
      disk->bus_type = MIDNIGHTCODE_DISK_BUS_SCSI;
    }
  }

  scsi_address[0] = 0;
  scsi_address[1] = 0;
  if(ioctl(fd, SCSI_IOCTL_GET_IDLUN, &scsi_address) >= 0) {
    disk->scsi_channel = (scsi_address[0] >> 16) & 0xff;
    disk->scsi_lun = (scsi_address[0] >> 8 ) & 0xff;
    disk->scsi_id = scsi_address[0] & 0xff;
  }

  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_l[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(disk->bus_type == MIDNIGHTCODE_DISK_BUS_UNKNOWN)
    disk->bus_type = MIDNIGHTCODE_DISK_BUS_SCSI;

  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;


  if(!disk)
    return 1;

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

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

  disk->bus_type = MIDNIGHTCODE_DISK_BUS_IDE;
  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_probe_cdrom(int fd,
                             struct midnightcode_disk_element_t * disk) {
  int drive_status;
  FILE *fp;


  if(!disk)
    return 1;
  if(disk->device_type != MIDNIGHTCODE_DISK_TYPE_WORM &&
    disk->device_type != MIDNIGHTCODE_DISK_TYPE_CDDVD)
    return 1;

  if((drive_status = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)) < 0)
    return 1;

  disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_UNKNOWN;
  disk->cd_media_available = 0;

  switch(drive_status) {

    case CDS_DISC_OK:
      disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_CLOSED;
      if((fp = fopen(disk->device, "r")) != NULL) {
        if(fseek(fp, 32808, SEEK_SET) == 0) {
          memset(disk->cd_media_label, 0, 64);
          if(fgets(disk->cd_media_label, 32, fp) != NULL)
            disk->cd_media_available = 1;
        }
      }
      fclose(fp);
      break;

    default:
      memset(disk->cd_media_label, 0, 64);
      if((drive_status = ioctl(fd, CDROM_DRIVE_STATUS, 0)) < 0)
        return 1;

      switch(drive_status) {

        case CDS_DISC_OK:
          disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_CLOSED;
          disk->cd_media_available = 1;
          break;

        case CDS_NO_DISC:
          disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_CLOSED;
          disk->cd_media_available = 0;
          break;

        case CDS_TRAY_OPEN:
          disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_OPEN;
          disk->cd_media_available = 0;
          break;

        default:
          disk->cd_tray_state = MIDNIGHTCODE_DISK_TRAY_UNKNOWN;
          disk->cd_media_available = 0;
          break;

      }
      break;

  }

  if(disk->cd_tray_state == MIDNIGHTCODE_DISK_TRAY_CLOSED &&
    disk->cd_media_available == 1) {

    disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_UNKNOWN;
    if((drive_status = ioctl(fd, CDROM_DISC_STATUS)) < 0)
      return 1;

    switch(drive_status) {

      case CDS_AUDIO:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_AUDIO;
        break;

      case CDS_DATA_1:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_DATA1;
        break;

      case CDS_DATA_2:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_DATA2;
        break;

      case CDS_XA_2_1:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_DATA3;
        break;

      case CDS_XA_2_2:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_DATA4;
        break;

      case CDS_MIXED:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_MIXED;
        break;

      default:
        disk->cd_media_type = MIDNIGHTCODE_DISK_MEDIA_UNKNOWN;
        break;
    }

  }

  return 0;
}



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


  if(!disk)
    return 1;
  if(disk->device_type != MIDNIGHTCODE_DISK_TYPE_WORM &&
    disk->device_type != MIDNIGHTCODE_DISK_TYPE_CDDVD)
    return 1;

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

  if(ioctl(fd, CDROMEJECT) == -1) {
    close(fd);
    return 1;
  }
  midnightcode_disk_probe_cdrom(fd, disk);

  close(fd);

  return 0;
}



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


  if(!disk)
    return 1;
  if(disk->device_type != MIDNIGHTCODE_DISK_TYPE_WORM &&
    disk->device_type != MIDNIGHTCODE_DISK_TYPE_CDDVD)
    return 1;

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

  if(ioctl(fd, CDROMCLOSETRAY) == -1) {
    close(fd);
    return 1;
  }
  midnightcode_disk_probe_cdrom(fd, disk);

  close(fd);

  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(!disk)
    return 1;

  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;


  if(!disk)
    return 1;

  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(disk->device_type == MIDNIGHTCODE_DISK_TYPE_WORM ||
    disk->device_type == MIDNIGHTCODE_DISK_TYPE_CDDVD) {
    midnightcode_disk_probe_cdrom(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(!elements)
    return -1;
  if(midnightcode_disk_list(elements) < 0)
    return -1;

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


  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;


  if(!elements)
    return -1;
  if(size <= 0)
    return -1;
  capacity = (long double)size * (long double)1000000000;

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


  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_l[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;
}

