/*****************************************************************************\
 *  $Id: ipmidetect.c,v 1.12 2008/08/12 18:14:38 chu11 Exp $
 *****************************************************************************
 *  Copyright (C) 2007-2008 Lawrence Livermore National Security, LLC.
 *  Copyright (C) 2007 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Albert Chu <chu11@llnl.gov>
 *  UCRL-CODE-228523
 *
 *  This file is part of Ipmidetect, tools and libraries for detecting
 *  IPMI nodes in a cluster. For details, see http://www.llnl.gov/linux/.
 *
 *  Ipmidetect 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.
 *
 *  Ipmidetect 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 Ipmidetect.  If not, see <http://www.gnu.org/licenses/>.
\*****************************************************************************/

#if HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#if STDC_HEADERS
#include <string.h>
#include <stdarg.h>
#endif /* STDC_HEADERS */

#if HAVE_GETOPT_H
#include <getopt.h>
#endif /* HAVE_GETOPT_H */
#include <dirent.h>
#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else  /* !TIME_WITH_SYS_TIME */
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else /* !HAVE_SYS_TIME_H */
#include <time.h>
#endif  /* !HAVE_SYS_TIME_H */
#endif /* !TIME_WITH_SYS_TIME */
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */
#include <assert.h>
#include <errno.h>

#include "ipmidetect.h"

#include "freeipmi-portability.h"
#include "error.h"
#include "fd.h"
#include "hostlist.h"

/* 
 * External variables for getopt 
 */
extern char *optarg;
extern int optind, opterr, optopt;

/* 
 * Definitions 
 */
#define IPMIDETECT_DETECTED_NODES                0  
#define IPMIDETECT_UNDETECTED_NODES              1
#define IPMIDETECT_DETECTED_AND_UNDETECTED_NODES 2

#define IPMIDETECT_BUFFERLEN         65536
#define IPMIDETECT_FORMATLEN         64
#define IPMIDETECT_OPTIONS_LEN       64
#define IPMIDETECT_LONG_OPTIONS_LEN  32
#define IPMIDETECT_MAXPATHLEN        256

/* 
 * Ipmidetect Data
 *
 * hostname - hostname to connect to
 * port - port to connect o
 * output_type - detected, undetected, or both output
 * output_format - hostlist, comma, newline, or space output
 * handle - ipmidetect handle
 * inputted_nodes - nodes input via the cmdline or stdin
 */
static char *hostname = NULL;
static int port = 0;
static int output_type = IPMIDETECT_DETECTED_AND_UNDETECTED_NODES;
static char output_format = 0;
static ipmidetect_t handle;
static hostlist_t inputted_nodes = NULL;

/* 
 * Ipmidetect output data
 */
static char detected_nodes[IPMIDETECT_BUFFERLEN];
static char undetected_nodes[IPMIDETECT_BUFFERLEN];
static char detectedfmt[IPMIDETECT_FORMATLEN];
static char undetectedfmt[IPMIDETECT_FORMATLEN];
static int detected_count = -1;
static int undetected_count = -1;

/* 
 * _init_ipmidetect
 *
 * initialize globals
 */
static void
_init_ipmidetect(void)
{
  if (!(handle = ipmidetect_handle_create()))
    err_exit("%s: ipmidetect_handle_create", __FUNCTION__);

  if (!(inputted_nodes = hostlist_create(NULL)))
    err_exit("%s: hostlist_create", __FUNCTION__);
}

/* 
 * _cleanup_ipmidetect
 *
 * cleanup globals
 */
static void
_cleanup_ipmidetect(void)
{
  (void)ipmidetect_handle_destroy(handle);
  hostlist_destroy(inputted_nodes);
}

/* 
 * _usage
 *
 * output usage and exit
 */
static void 
_usage(void) 
{
  fprintf(stderr,
	  "Usage: ipmidetect [OPTIONS]... [NODES]...\n"
	  "  -h         --help              Print help and exit\n"
	  "  -v         --version           Print version and exit\n"
	  "  -o STRING  --hostname=STRING   Server hostname\n"
	  "  -p INT     --port=INT          Server port\n"
	  "  -d         --detected          Output only detected nodes\n"
	  "  -u         --undetected        Output only undetected nodes\n"
	  "  -q         --hostrange         Output in hostrange format\n"
	  "  -c         --comma             Output in comma separated list\n"
	  "  -n         --newline           Output in newline separated list\n"
	  "  -s         --space             Output in space separated list\n");

  fprintf(stderr, "\n");
  exit(1);
}

/*  
 * _version
 *
 * output version and exit
 */
static void 
_version(void) 
{
  fprintf(stderr, "ipmidetect %s\n", VERSION);
  exit(1);
}

/* 
 * _push_inputted_nodes
 *
 * push nodes onto a hostlist structure
 */
static void
_push_inputted_nodes(char *nodes) 
{
  assert(nodes);

  /* Error if nodes aren't short hostnames */
  if (strchr(nodes, '.'))
    err_exit("nodes must be listed in short hostname format");
        
  if (!hostlist_push(inputted_nodes, nodes))
    err_exit("nodes improperly formatted");
}

/* 
 * _read_nodes_from_stdin
 *
 * read nodes off of standard input rather than the command line
 */
static void 
_read_nodes_from_stdin(void) 
{
  char buf[IPMIDETECT_BUFFERLEN];
  int n;
  
  if ((n = fd_read_n(STDIN_FILENO, buf, IPMIDETECT_BUFFERLEN)) < 0)
    err_exit("error reading from standard input: %s", strerror(errno));
  
  if (n == IPMIDETECT_BUFFERLEN)
    err_exit("overflow in standard input buffer"); 

  if (n > 0) 
    {
      char *ptr = strtok(buf, " \t\n\0"); 
      while (ptr) 
        {
          _push_inputted_nodes(ptr);
          ptr = strtok(NULL, " \t\n\0");
        }
    }
}

/* 
 * _cmdline_parse
 *
 * parse all cmdline input
 */
static void
_cmdline_parse(int argc, char **argv) 
{
  int c, index;
  char soptions[IPMIDETECT_OPTIONS_LEN+1];
  char *ptr;

#if HAVE_GETOPT_LONG
  struct option loptions[IPMIDETECT_LONG_OPTIONS_LEN+1] = 
    {
      {"help",         0, NULL, 'h'},
      {"version",      0, NULL, 'v'},
      {"hostname",     1, NULL, 'o'},
      {"port",         1, NULL, 'p'},
      {"detected",     0, NULL, 'd'},
      {"undetected",   0, NULL, 'u'},
      {"hostrange",    0, NULL, 'q'},
      {"comma",        0, NULL, 'c'},
      {"newline",      0, NULL, 'n'},
      {"space",        0, NULL, 's'},
      {0, 0, 0, 0},
    };
#endif /* HAVE_GETOPT_LONG */

  assert(argv);

  memset(soptions, '\0', IPMIDETECT_OPTIONS_LEN+1);
  strncpy(soptions, "hvo:p:duqcns", IPMIDETECT_OPTIONS_LEN);

  /* turn off output messages printed by getopt_long */
  opterr = 0;

#if HAVE_GETOPT_LONG
  while ((c = getopt_long(argc, argv, soptions, loptions, NULL)) != -1)
#else
  while ((c = getopt(argc, argv, options)) != -1)
#endif
      {
        switch(c) 
          {
          case 'h':
            _usage();
          case 'v':
            _version();
          case 'o':
            hostname = optarg;
            break;
          case 'p':
            port = strtol(optarg, &ptr, 10);
            if (ptr != (optarg + strlen(optarg)))
              err_exit("invalid port specified");
            break;
          case 'u':
            output_type = IPMIDETECT_UNDETECTED_NODES;
            break;
          case 'd':
            output_type = IPMIDETECT_DETECTED_NODES;
            break;
          case 'q':
            output_format = 0;
            break;
          case 'c':
            output_format = ',';
            break;
          case 'n':
            output_format = '\n';
            break;
          case 's':
            output_format = ' ';
            break;
          default:
          case '?':
            fprintf(stderr, "command line option error\n");
            _usage();
          }
      }

  index = optind;
  
  /* Read nodes in from the command line or standard input */
  if (index < argc) 
    {
      if (!strcmp(argv[index], "-"))
        _read_nodes_from_stdin();
      else 
        {
          while (index < argc) 
            {
              _push_inputted_nodes(argv[index]);
              index++;
            }
        } 
      
      /* remove any duplicate nodes listed */
      hostlist_uniq(inputted_nodes);
    }
}

/* 
 * _get_input_nodes
 *
 * Get the detected or undetected status of nodes specified on the
 * cmdline or standard input.
 */
static void 
_get_input_nodes(char *buf, int buflen, int which)
{
  hostlist_t hl = NULL;
  hostlist_iterator_t iter = NULL;
  char *node = NULL;

  assert(buf && buflen);
  assert(which == IPMIDETECT_DETECTED_NODES || which == IPMIDETECT_UNDETECTED_NODES);

  if (!(hl = hostlist_create(NULL)))
    err_exit("%s: hostlist_create()", __FUNCTION__);

  if (!(iter = hostlist_iterator_create(inputted_nodes)))
    err_exit("%s: hostlist_iterator_create()", __FUNCTION__);

  while ((node = hostlist_next(iter))) 
    {
      int rv;

      if (which == IPMIDETECT_DETECTED_NODES) 
        rv = ipmidetect_is_node_detected(handle, node);
      else
        rv = ipmidetect_is_node_undetected(handle, node);

      if (rv < 0)
        {
          if (ipmidetect_errnum(handle) == IPMIDETECT_ERR_NOTFOUND)
            err_exit("Unknown node \"%s\"", node);
          else 
            {
              char *msg = ipmidetect_errormsg(handle);
              if (which == IPMIDETECT_DETECTED_NODES)
                err_exit("%s: ipmidetect_is_node_detected(): %s", __FUNCTION__, msg);
              else
                err_exit("%s: ipmidetect_is_node_undetected(): %s", __FUNCTION__, msg);
            }
        }

      if (rv) 
        {
          if (!hostlist_push_host(hl, node))
            err_exit("%s: hostlist_push_host()", __FUNCTION__);
        }

      free(node);
    }
  
  hostlist_sort(hl);
  
  if (hostlist_ranged_string(hl, buflen, buf) < 0)
    err_exit("%s: hostlist_ranged_string()", __FUNCTION__);
  
  hostlist_iterator_destroy(iter);
  hostlist_destroy(hl);
}
 
/* 
 * _get_all_nodes
 *
 * Get the detected or undetected status of all nodes.
 */
static void
_get_all_nodes(char *buf, int buflen, int which)
{
  int rv;

  assert(buf && buflen > 0);
  assert(which == IPMIDETECT_DETECTED_NODES || which == IPMIDETECT_UNDETECTED_NODES);

  if (which == IPMIDETECT_DETECTED_NODES) 
    rv = ipmidetect_get_detected_nodes_string(handle, buf, buflen);
  else
    rv = ipmidetect_get_undetected_nodes_string(handle, buf, buflen);

  if (rv < 0)
    {
      char *msg = ipmidetect_errormsg(handle);
      if (which == IPMIDETECT_DETECTED_NODES) 
        err_exit("%s: ipmidetect_get_detected_nodes_string(): %s", __FUNCTION__, msg);
      else
        err_exit("%s: ipmidetect_get_undetected_nodes_string(): %s", __FUNCTION__, msg);
    }
}

/*  
 * _get_nodes
 *
 * get the detected or undetected status of nodes.  The appropriate function call
 * to _get_input_nodes or _get_all_nodes will be done.
 */
static void 
_get_nodes(char *buf, int buflen, int which, int *count) 
{
  hostlist_t hl = NULL;
  
  assert(buf && buflen > 0 && count);
  assert(which == IPMIDETECT_DETECTED_NODES || which == IPMIDETECT_UNDETECTED_NODES);

  if (hostlist_count(inputted_nodes) > 0)
    _get_input_nodes(buf, buflen, which);
  else
    _get_all_nodes(buf, buflen, which);
  
  if (!(hl = hostlist_create(buf)))
    err_exit("%s: hostlist_create()", __FUNCTION__);
  
  *count = hostlist_count(hl);

  hostlist_destroy(hl);
}

/* 
 * _output_nodes
 * 
 * output the nodes specified in the nodebuf to stdout
 */
static void
_output_nodes(char *nodebuf) 
{
  assert(nodebuf);

  if (!output_format)
    fprintf(stdout, "%s\n", nodebuf);
  else 
    {
      char tbuf[IPMIDETECT_BUFFERLEN];
      hostlist_t hl = NULL;
      char *ptr;

      /* output nodes separated by some break type */
      memset(tbuf, '\0', IPMIDETECT_BUFFERLEN);
    
      if (!(hl = hostlist_create(nodebuf)))
        err_exit("%s: hostlist_create() error", __FUNCTION__);
      
      if (hostlist_deranged_string(hl, IPMIDETECT_BUFFERLEN, tbuf) < 0)
        err_exit("%s: hostlist_deranged_string() error", __FUNCTION__);
      
      /* convert commas to appropriate break types */
      if (output_format != ',') 
        {
          while ((ptr = strchr(tbuf, ',')))
            *ptr = (char)output_format;
        }

      /* start on the next line if its a newline separator */
      if (output_type == IPMIDETECT_DETECTED_AND_UNDETECTED_NODES && output_format == '\n')
        fprintf(stdout, "\n");

      fprintf(stdout,"%s\n", tbuf);
      hostlist_destroy(hl);
    }
}

/* 
 * _log10
 *
 * portable log10() function that also allows us to avoid linking
 * against the math library.
 */
static int 
_log10(int num) 
{
  int count = 0;

  if (num > 0) 
    {
      while ((num /= 10) > 0)
        count++;
    }

  return count;
} 

/* 
 * _create_formats
 *
 * create the output formats based on the detected and undetected count.
 */
static void
_create_formats(char *endstr)
{
  int max;

  assert(detected_count >= 0 && undetected_count >= 0);

  if (detected_count > undetected_count)
    max = _log10(detected_count);
  else
    max = _log10(undetected_count);

  max++;

  snprintf(detectedfmt,   IPMIDETECT_FORMATLEN, "detected:   %%%dd%s", max, endstr);
  snprintf(undetectedfmt, IPMIDETECT_FORMATLEN, "undetected: %%%dd%s", max, endstr);
}

/* 
 * _output_data
 *
 * Output the current upundetected state based on inputs from the command line
 *
 * Returns exit_val;
 */
int
_output_data(void)
{
  int exit_val;

  if (ipmidetect_load_data(handle, hostname, port, 0) < 0) 
    {
      int errnum = ipmidetect_errnum(handle);
      char *msg = ipmidetect_errormsg(handle);

      /* Check for "legit" errors and output appropriate message */
      if (errnum == IPMIDETECT_ERR_CONF_PARSE)
        err_exit("Parse error in conf file");
      else if (errnum == IPMIDETECT_ERR_CONNECT) 
        err_exit("Cannot connect to server");
      else if (errnum == IPMIDETECT_ERR_CONNECT_TIMEOUT)
        err_exit("Timeout connecting to server");
      else if (errnum == IPMIDETECT_ERR_HOSTNAME_INVALID)
        err_exit("Invalid hostname");
      else
        err_exit("%s: ipmidetect_load_data(): %s", __FUNCTION__, msg);
    }
  
  _get_nodes(detected_nodes, 
             IPMIDETECT_BUFFERLEN, 
             IPMIDETECT_DETECTED_NODES,
             &detected_count);

  _get_nodes(undetected_nodes, 
             IPMIDETECT_BUFFERLEN,
             IPMIDETECT_UNDETECTED_NODES, 
             &undetected_count);
  
  /* output up, undetected, or both up and undetected nodes */
  if (output_type == IPMIDETECT_DETECTED_AND_UNDETECTED_NODES) 
    {
      if (output_format == '\n')
        {
          /* newline output is funny, thus special */
          snprintf(detectedfmt,   
                   IPMIDETECT_FORMATLEN, 
                   "detected %d:", 
                   detected_count);
          snprintf(undetectedfmt, 
                   IPMIDETECT_FORMATLEN, 
                   "undetected %d:", 
                   undetected_count);
        }
      else
        _create_formats(": ");
          
      fprintf(stdout, detectedfmt, detected_count);
          
      _output_nodes(detected_nodes);
          
      /* handle odd situation with newline output list */
      if (output_format == '\n')
        fprintf(stdout, "\n");
      
      fprintf(stdout, undetectedfmt, undetected_count);
          
      _output_nodes(undetected_nodes);
    }
  else if (output_type == IPMIDETECT_DETECTED_NODES)
    _output_nodes(detected_nodes);
  else
    _output_nodes(undetected_nodes);
  
  if (output_type == IPMIDETECT_DETECTED_AND_UNDETECTED_NODES)
    exit_val = 0;
  else if (output_type == IPMIDETECT_DETECTED_NODES)
    exit_val = (!undetected_count) ? 0 : 1;
  else
    exit_val = (!detected_count) ? 0 : 1;
  
  return exit_val;
}

int 
main(int argc, char *argv[]) 
{
  int exit_val;

  err_init(argv[0]);
  err_set_flags(ERROR_STDERR);

  _init_ipmidetect();

  _cmdline_parse(argc, argv);

  exit_val = _output_data();

  _cleanup_ipmidetect();
  exit(exit_val);
}
