/*

                                   _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-2007
       "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-init.c :: Midnight Code init assister library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <fcntl.h>
#include <linux/sockios.h>
#include <linux/if.h>
#include <signal.h>
#include <linux/kd.h>
#if defined(__GLIBC__)
#include <sys/reboot.h>
#define midnightcode_init_reboot(token) reboot(token)
#else
#define midnightcode_init_reboot(token) reboot(0xfee1dead, 672274793, token)
#endif

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-colour.h"
#include "midnightcode-status.h"
#include "midnightcode-config.h"
#include "midnightcode-parse.h"
#include "midnightcode-disk.h"
#include "midnightcode-camera.h"
#include "midnightcode-interface.h"
#include "midnightcode-resolve.h"
#include "midnightcode-route.h"
#include "midnightcode-process.h"
#include "midnightcode-terminal.h"
#include "midnightcode-init.h"



int
midnightcode_init_process(char * label, char * process, int options,
                                       int terminal) {
  int pid;


  options |= MIDNIGHTCODE_PROCESS_OPTION_WAITTERM;
  options |= MIDNIGHTCODE_PROCESS_OPTION_NULLIO;
  options |= MIDNIGHTCODE_PROCESS_OPTION_EXITCODE;
  terminal = MIDNIGHTCODE_TERMINAL_VIRTUAL_NULL;

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL, label);
  if((pid = midnightcode_process(process, options, terminal)) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }


  return pid;
}



int
midnightcode_init_sysctl(char * label, char * control, char * value) {
  char * control_buf;
  int control_size;
  int ret_i;


  if(!label)
    return -1;
  if(!control)
    return -1;
  if(!value)
    return -1;
  if(strlen(control) > 255)
    return -1;

  control_size = strlen(control) + 1;
  control_buf = malloc(control_size);
  if(!control_buf)
    return -1;
  memset(control_buf, 0, control_size);
  strncpy(control_buf, control, control_size);
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL, label);

  ret_i = midnightcode_process_sysctl(control_buf, value);
  free(control_buf);
  if(ret_i < 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    if(ret_i == 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);
    } else {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
  }


  return ret_i;
}



int
midnightcode_init_config_load(char *  config_file,
              struct midnightcode_config_head_t * elements,
              int default_state)  {
  int ret_i;


  if(!elements)
    return -1;
  if(default_state < 0 || default_state > 1)
    return -1;

  ret_i = 0;
  if(default_state) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Loading default device configuration profile");
  } else {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Loading device configuration profile");
  }
  if((ret_i = midnightcode_config_read_file(config_file,
    elements, MIDNIGHTCODE_CONFIG_TYPE_NEWSET,
    MIDNIGHTCODE_CONFIG_OPTION_AUTONEW |
    MIDNIGHTCODE_CONFIG_OPTION_MERGEARGS |
    MIDNIGHTCODE_CONFIG_OPTION_SILENT )) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }


  return ret_i;
}



int
midnightcode_init_config_retrieve(
              struct midnightcode_disk_head_t * disk_list_head_p,
              struct midnightcode_interface_head_t * interface_list_head_p,
              struct midnightcode_route_head_t * route_list_head_p,
              struct midnightcode_resolve_domain_head_t * rdomain_list_head_p,
              struct midnightcode_resolve_server_head_t * rserver_list_head_p,
              struct midnightcode_config_head_t * config_list_head_p) {
  int ret_i;


  if(!disk_list_head_p)
    return -1;
  if(!interface_list_head_p)
    return -1;
  if(!route_list_head_p)
    return -1;
  if(!rdomain_list_head_p)
    return -1;
  if(!rserver_list_head_p)
    return -1;
  if(!config_list_head_p)
    return -1;

  ret_i = MIDNIGHTCODE_INIT_CONFIG_NONE;

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Retrieving disk devices");
  if(midnightcode_disk_from_config(disk_list_head_p,
    config_list_head_p) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_CONFIG_DISK;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Retrieving network devices");
  if(midnightcode_interface_from_config(interface_list_head_p,
    config_list_head_p) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_CONFIG_INTERFACE;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Reducing retrieved interfaces to ethernet subset");
  if(midnightcode_interface_only(interface_list_head_p) <= 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Retrieving routing data");
  if(midnightcode_route_from_config(route_list_head_p,
    config_list_head_p) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_CONFIG_ROUTE;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Retrieving resolver data");
  if(midnightcode_resolve_from_config(rdomain_list_head_p,
    rserver_list_head_p, config_list_head_p) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_CONFIG_RESOLVE;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }


  return ret_i;
}



int
midnightcode_init_hardware(
              struct midnightcode_disk_head_t * disk_list_head_p,
              struct midnightcode_interface_head_t * interface_list_head_p) {
  int ret_i;


  ret_i = MIDNIGHTCODE_INIT_HARDWARE_NONE;

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Probing disk devices");
  if(midnightcode_disk_list(disk_list_head_p) < 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_HARDWARE_DISK;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Probing network devices");
  if(midnightcode_interface_list(interface_list_head_p) < 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_HARDWARE_INTERFACE;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }


  return ret_i;
}



int
midnightcode_init_peripherals(
              struct midnightcode_camera_head_t * camera_list_head_p) {
  int ret_i;


  ret_i = MIDNIGHTCODE_INIT_HARDWARE_NONE;

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Probing camera devices");
  if(midnightcode_camera_list(camera_list_head_p) < 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
  } else {
    ret_i |= MIDNIGHTCODE_INIT_HARDWARE_CAMERA;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  }


  return ret_i;
}



int
midnightcode_init_networking(
              struct midnightcode_interface_head_t * elements,
              struct midnightcode_interface_element_t ** element) {


  if(!elements)
    return -1;

  // verfiy network
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Verifying kernel network support");
  if(!midnightcode_interface_support()) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_BLURB,
      MIDNIGHTCODE_STATUS_STYLE_UNKNOWN,
      "Error - This kernel will not support networking.");
    sleep(5);
    return -1;
  }
  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);

  // do we have eth devices
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Identifying ethernet network devices");
  if(midnightcode_interface_only(elements) <= 0) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_BLURB,
      MIDNIGHTCODE_STATUS_STYLE_UNKNOWN,
      "Error - No supported network interface cards.");
    sleep(5);
    return -1;
  }
  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);

  // are any with   flags |= IFF_UP
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Identifying up ethernet network device");
  midnightcode_list_for_each((*element), elements) {
    if(!((*element)->flags & IFF_UP))
      continue;
    if(!((*element)->flags & IFF_RUNNING))
      continue;
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    return 1;
  }
  *element = NULL;
  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);


  return 0;
}



int
midnightcode_init_mountfs(int options) {
  int ret_i;


  /* TODO: consider "mount -t usbfs usbfs /proc/bus/usb" */
  ret_i = MIDNIGHTCODE_INIT_FS_NONE;

  if (options & MIDNIGHTCODE_INIT_FS_PROC) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Mounting proc filesystem");
    if(mount("none", "/proc", "proc", MS_MGC_VAL, NULL) != 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    } else {
      ret_i |= MIDNIGHTCODE_INIT_FS_PROC;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
  }

  if (options & MIDNIGHTCODE_INIT_FS_SYS) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Mounting sys filesystem");
    if(mount("none", "/sys", "sysfs", MS_MGC_VAL, NULL) != 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    } else {
      ret_i |= MIDNIGHTCODE_INIT_FS_SYS;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
  }

  if (options & MIDNIGHTCODE_INIT_FS_PTS) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Mounting pts filesystem");
    if(mount("none", "/dev/pts", "devpts", MS_MGC_VAL, NULL) != 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    } else {
      ret_i |= MIDNIGHTCODE_INIT_FS_PTS;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
  }

  if (options & MIDNIGHTCODE_INIT_FS_DEV) {
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Mounting dev filesystem");
    if(mount("none", "/dev", "devfs", MS_MGC_VAL, NULL) != 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    } else {
      ret_i |= MIDNIGHTCODE_INIT_FS_DEV;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
  }


  return ret_i;
}



int
midnightcode_init_nic_lo(
              struct midnightcode_interface_head_t * elements) {
  struct midnightcode_interface_element_t * element;


  if(!elements)
    return -1;

  element = NULL;
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Instantiate network interface lo");

  if(midnightcode_interface_byname(elements, "lo", &element) != 1) {
    if(midnightcode_interface_add(elements, "lo", &element) != 1) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
      return -1;
    }
  }

  snprintf(element->ipv4_address_s, 31, "127.0.0.1");
  snprintf(element->ipv4_mask_s, 31, "255.0.0.0");

  memset(&element->ipv4_address, 0, sizeof(struct in_addr));
  memset(&element->ipv4_mask, 0, sizeof(struct in_addr));
  if(midnightcode_parse_strtovar((void *)&element->ipv4_address,
    element->ipv4_address_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_IP)) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    return -1;
  }
  if(midnightcode_parse_strtovar((void *)&element->ipv4_mask,
    element->ipv4_mask_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_IP)) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    return -1;
  }

  if(midnightcode_interface_configure(element) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    return -1;
  }
  if(midnightcode_interface_up(element) != 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    return -1;
  }

  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);

  /* NOTE: Not required under kernel 2.6 */
  /*
  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Route network interface lo");
  if(midnightcode_route_add("127.0.0.0", "255.0.0.0", "0.0.0.0",
    element->name) < 0) {
    midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
    return -1;
  }

  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
  */


  return 0;
}



int
midnightcode_init_usbmedia(
              struct midnightcode_disk_head_t ** elements,
              char * label,
              struct midnightcode_disk_element_t ** element,
              int sleep_cycle) {
  struct midnightcode_disk_element_t * old_element;
  int sleep_for;
  int ret_i;


  if(!elements)
    return -1;
  if(!element)
    return -1;
  if(!label)
    return -1;
  if(sleep_cycle < 1)
    return -1;

  ret_i = -1;
  sleep_for = 4;

  while(ret_i != 1 && sleep_cycle > 0) {
    if((sleep_cycle - sleep_for) < 0)
      sleep_for = sleep_cycle;
    sleep(sleep_for);
    sleep_cycle -= sleep_for;
    old_element = NULL;
    midnightcode_list_for_each((*element), (*elements)) {
      old_element = (*element);
      midnightcode_list_remove((*elements), (*element));
      if(old_element != NULL) {
        free(old_element);
        old_element = NULL;
      }
    }
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Probing disk devices");
    if(midnightcode_disk_list((*elements)) < 0) {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_FAIL);
      continue;
    } else {
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
    }
    midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
      MIDNIGHTCODE_STATUS_STYLE_NORMAL,
      "Seeking USB boot device");
    midnightcode_list_for_each((*element), (*elements)) {
      if((*element)->device_type != MIDNIGHTCODE_DISK_TYPE_DISK)
        continue;
      if((*element)->bus_type != MIDNIGHTCODE_DISK_BUS_USB)
        continue;
      if((*element)->scsi_emulation != MIDNIGHTCODE_DISK_EMULATION_ENABLED)
        continue;
      if((*element)->scsi_lun != 0)
        continue;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
      ret_i = 1;
      /* TODO: Volume label validation ? 
      if(strncmp((*element)->cd_media_label, label, strlen(label)) == 0) {
        midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
        ret_i = 1;
      }
      */
      break;
    }
    if(ret_i != 1) {
      *element = NULL;
      midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN); 
    }
  }


  return ret_i;
}



int
midnightcode_init_cdmedia(
              struct midnightcode_disk_head_t * elements,
              char * label,
              struct midnightcode_disk_element_t ** element) {


  if(!elements)
    return -1;
  if(!element)
    return -1;
  if(!label)
    return -1;

  midnightcode_status_text(MIDNIGHTCODE_STATUS_TYPE_TEXT,
    MIDNIGHTCODE_STATUS_STYLE_NORMAL,
    "Seeking optical device with media");

  midnightcode_list_for_each((*element), elements) {
    if((*element)->device_type == MIDNIGHTCODE_DISK_TYPE_WORM ||
      (*element)->device_type == MIDNIGHTCODE_DISK_TYPE_CDDVD) {
      if((*element)->cd_tray_state == MIDNIGHTCODE_DISK_TRAY_CLOSED &&
        (*element)->cd_media_available && 
        ((*element)->cd_media_type >= MIDNIGHTCODE_DISK_MEDIA_DATA1 &&
        (*element)->cd_media_type <= MIDNIGHTCODE_DISK_MEDIA_DATA4)) {
        if(strncmp((*element)->cd_media_label, label, strlen(label)) == 0) {
          midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_OKAY);
          return 1;
        }
      }
    }
  }
  *element = NULL;
  midnightcode_status_flag(MIDNIGHTCODE_STATUS_FLAG_WARN);


  return 0;
}



int
midnightcode_init_boot(int token)  {
  int terminal_fd;


  midnightcode_init_reboot(token);
  if(midnightcode_terminal_console_open(&terminal_fd) == 0) {
    ioctl(terminal_fd, KDSIGACCEPT, SIGWINCH);
    close(terminal_fd);
  } else {
    ioctl(0, KDSIGACCEPT, SIGWINCH);
  }

  return 0;
}


