/*
 * nasd_cheops_mgr_main.c
 *
 * Main program for Cheops storage manager
 *
 * Authors: Khalil Amiri, CMU SCS/ECE, July 18 1997
 *          Sean Levy, CMU SCS, July 1999
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */

#include <nasd/nasd_options.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_cheops_types.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_timeout.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_cheops_mgr.h>
#include <nasd/nasd_cheops_mgr_common.h>
#include <nasd/nasd_cheops_mgr_internal.h>
#include <nasd/nasd_getopt.h>
#include <nasd/nasd_od.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* Possible debugging */
#define NASD_TEST_DRIVE

#define DEF_STACK_SIZE          32768
#define DEF_SVC_THREADS         5
#define DEF_SVC_IPPORT          6663

nasd_thread_id_t nasd_cheops_mgr_master_shutdown_thread_id;
nasd_threadgroup_t nasd_cheops_mgr_master_shutdown_thread_group;
nasd_thread_t nasd_cheops_mgr_shutdown_worker_thread;
sig_atomic_t nasd_cheops_mgr_shutdown_signalled = 0;
int alldone = 0;

int nasd_cheops_mgr_stopped = 0;

pid_t nasd_cheops_mgr_signal_pid;

int signal_pipe[2];
#define signal_pipe_rd  signal_pipe[0]
#define signal_pipe_wr  signal_pipe[1]

#define CLOSEUP_PIPES() {                                               \
  close(signal_pipe[0]);                                                \
  close(signal_pipe[1]);                                                \
}

static void handle_interrupt(nasd_threadarg_t ignored);
char *progname = "cheops_mgr";

static void
nasd_cheops_mgr_shutdown_worker_proc(
  nasd_threadarg_t  ignored)
{
  fd_set mrdb, rdb;
  int ret;

  nasd_cheops_mgr_signal_pid = getpid();

  nasd_cheops_mgr_master_shutdown_thread_id = nasd_thread_self();
  NASD_THREADGROUP_RUNNING(&nasd_cheops_mgr_master_shutdown_thread_group);

  FD_ZERO(&mrdb);
  FD_SET(signal_pipe_rd, &mrdb);

  while(!nasd_cheops_mgr_shutdown_signalled) {
    rdb = mrdb;
    ret = select(signal_pipe_rd+1, &rdb, NULL, NULL, NULL);
    if (nasd_cheops_mgr_shutdown_signalled || FD_ISSET(signal_pipe_rd, &mrdb))
      break;
    if (ret) {
      fprintf(stderr, "CHEOPS MGR WARNING: shutdown worker thread got "
        "ret %d errno %d\n", ret, errno);
      fflush(stderr);
    }
  }

  handle_interrupt(NULL);

  /* I don't think we should ever get back here. */
}

void
handle_sigint(
  int  sig)
{
  int ret;
  char c;

  nasd_cheops_mgr_stopped = 1;
  c = 'a';
  ret = write(signal_pipe_wr, &c, 1);
}

static void
handle_interrupt(
  nasd_threadarg_t  ignored)
{
  nasd_thread_id_t self;
  nasd_status_t rc;

  extern int nasd_mem_use_counter;

  self = nasd_thread_self();

  fprintf(stderr, "INTERRUPTED (thread id %" NASD_THREAD_ID_FMT ")\n", self);
  fflush(stderr);

  nasd_cheops_mgr_stop_rpc();

  rc = nasd_cheops_mgr_really_shutdown();
  if (rc) {
    fprintf(stderr,
      "CHEOPS MGR: got 0x%x (%s) from nasd_edrfs_really_shutdown()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  nasd_mem_shutdown();

  fprintf(stderr, "Completed shutdown.\n");
  fflush(stderr);
  fflush(stdout);
#if NASD_MEM_COUNT_ALLOC > 0
  if (nasd_mem_allocated) {
    fprintf(stderr,
            "WARNING: %" NASD_MEMALLOC_FMT
            " bytes of memory still outstanding, something leaked core.\n",
            nasd_mem_allocated);
    fprintf(stderr, "memory subsystem use counter at %d\n", nasd_mem_use_counter);
    fflush(stderr);
  }
  else {
    printf("0 bytes of memory still outstanding.\n");
  }
#endif /* NASD_MEM_COUNT_ALLOC > 0 */
  fflush(stdout);
  fflush(stderr);

  CLOSEUP_PIPES();
  alldone = 1;
  exit(0);
}

void
usage(
  char  *fmt,
  ...)
{
  va_list ap;

  if (progname == NULL)
    progname = "cheops_mgr";
  if (fmt == NULL)
    fprintf(stderr, "%s: version 0.2 Copyright (C) 1997,1998,1999 "
            "Carnegie Mellon University.\n", progname);
  else {
    fprintf(stderr, "%s: ", progname);
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    if (fmt[strlen(fmt)-1] != '\n')
      fprintf(stderr, "\n");
  }
  fprintf(stderr,
          "usage: %s [-u stripe_unit] [-r raid_level] [-w stripe_width]\n"
          "          [-R res_file] [-D dmap_file] [-I imap_file] [-t svc_threads]\n"
          "          [-s stack_sz] [-p port] [-P partition_id] [-z part_sz]\n"
          "          [-klrqcOh]\n",
          progname);
  exit(1);
}

void
nerror_ap(
  nasd_status_t  rc,
  char          *fmt,
  va_list        ap)
{
  fprintf(stderr, "%s: got 0x%x (%s) ", progname, rc, nasd_error_string(rc));
  if (fmt == NULL)
    fprintf(stderr, "in unknown location\n");
  else {
    vfprintf(stderr, fmt, ap);
    if (fmt[strlen(fmt)-1] != '\n')
      fprintf(stderr, "\n");
  }
}

void
nerror(
  nasd_status_t  rc,
  char          *fmt,
  ...)
{
  va_list ap;

  va_start(ap, fmt);
  nerror(rc, fmt, ap);
  va_end(ap);
}

void
nerror_die(
  nasd_status_t  rc,
  char          *fmt,
  ...)
{
  va_list ap;

  va_start(ap, fmt);
  nerror(rc, fmt, ap);
  va_end(ap);
  fprintf(stderr, "%s: fatal error - exiting.\n", progname);
  exit(1);
}

void
perror_die(
  char          *fmt,
  ...)
{
  extern int errno;
  char errstr[512];
  va_list ap;

  if ((errno < 0) || (errno >= sys_nerr))
    snprintf(errstr, sizeof(errstr), "[unknown unix error code %d]", errno);
  else {
    strncpy(errstr, sys_errlist[errno], sizeof(errstr)-1);
    errstr[sizeof(errstr)-1] = '\0';
  }
  fprintf(stderr, "%s: unix error %d (%s) ", progname, errno, errstr);
  if (fmt == NULL)
    fprintf(stderr, "in unknown location\n");
  else {
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    if (fmt[strlen(fmt)-1] != '\n')
      fprintf(stderr, "\n");
  }
  exit(1);
}

int
main(
  int     argc,
  char  **argv)
{
  nasd_uint16 ipport;
  int svc_threads;
  int stack_size;
  char c;
  int ret;
  nasd_status_t rc;
  int nondefault_binding;
  int binding_type;
  char *resource_file;
  char *dmap_file;
  char *imap_file;
  int def_partition_size;
  int stripe_unit;
  int stripe_width;
  int raid_level;
  int partition_id;
  int quietude;
  int clear_old_state;
  int override_defaults;

  progname = strrchr(argv[0], '/');
  if (progname)
    progname++;
  else
    progname = argv[0];
  binding_type = NASD_BIND_DEFAULT;
  resource_file = NULL;
  dmap_file = NULL;
  imap_file = NULL;
  def_partition_size = 0;
  stripe_unit = 0;
  stripe_width = 0;
  raid_level = 0;
  nondefault_binding = 0;
  partition_id = -1;
  ipport = 0;
  svc_threads = 0;
  stack_size = 0;
  quietude = 0;
  clear_old_state = 0;
  override_defaults = 0;

  setbuf(stderr, NULL);

  /* Parse CLA */
  while (nasd_getopt(argc, argv, "cklOr:R:D:I:w:u:t:s:p:P:z:hq", &c)) {
    switch (c) {
    case 'c':
      clear_old_state++;
      break;
    case 'O':
      override_defaults++;
    case 'k':
      if (nondefault_binding)
        usage("binding specified multiple times");
      nondefault_binding = 1;
      binding_type = NASD_BIND_KPDEV_DEFAULT;
      break;
    case 'l':                           /* coLocate */
      if (nondefault_binding)
        usage("binding specified multiple times");
      nondefault_binding = 1;
      binding_type = NASD_BIND_COLOCATE;
      break;
    case 'R':                           /* Resource file */
      if (resource_file != NULL)
        usage("resource file specified multiple times (first was %s)",
              resource_file);
      resource_file = strdup(nasd_optarg);
      break;
    case 'D':                           /* Dmap file */
      if (dmap_file != NULL)
        usage("dmap file specified multiple times (first was %s)", dmap_file);
      dmap_file = strdup(nasd_optarg);
      break;
    case 'I':                           /* Imap file */
      if (imap_file == NULL)
        usage("imap file specified multiple times (first was %s)", imap_file);
      imap_file = strdup(nasd_optarg);
      break;
    case 'r':                           /* RAID level */
      if (raid_level != 0)
        usage("RAID level specified multiple times");
      else {
        int temp;

        if (sscanf(nasd_optarg, "%d", &temp) != 1)
          usage("argument to -r does not scan as int");
        switch (temp) {
        case 0:
          raid_level = RAID0;
          break;
        case 1:
          raid_level = NORAID;
          break;
        case 2:
          raid_level = RAID1;
          break;
        case 3:
          raid_level = RAID5;
          break;
        case 4:
          raid_level = LAST_DEFINED_LAYOUT;
          break;
        default:
          usage("unrecognized RAID level (0=RAID0,1=NORAID,2=RAID1,3=RAID5)");
          break;
        }
      }
      break;
    case 'w':                           /* stripe Width */
      if (stripe_width)
        usage("stripe width specified multiple times (first was %d)",
              stripe_width);
      else if (sscanf(nasd_optarg, "%d", &stripe_width) != 1)
        usage("argument to -w does not scan as integer");
      break;
    case 'u':                           /* stripe Unit */
      if (stripe_unit)
        usage("stripe unit specified multiple times (first was %d)",
              stripe_unit);
      else if (sscanf(nasd_optarg, "%d", &stripe_unit) != 1)
        usage("argument to -u does not scan as integer");
      else
        stripe_unit *= 1024;
      break;
    case 't':                           /* number of service Threads */
      if (svc_threads)
        usage("service threads specified multiple times (first was %d)",
              svc_threads);
      else if (sscanf(nasd_optarg, "%d", &svc_threads) != 1)
        usage("argument to -t does not scan as integer");
      break;
    case 's':                           /* stack size */
      if (stack_size)
        usage("stack size specified multiple times (first was %d)",
              stack_size);
      else if (sscanf(nasd_optarg, "%d", &stack_size) != 1)
        usage("argument to -s does not scan as integer");
      break;
    case 'z':                           /* default partition size */
      if (def_partition_size)
        usage("default partition specified multiple times (first was %d)",
              def_partition_size);
      else if (sscanf(nasd_optarg, "%d", &def_partition_size) != 1)
        usage("argument to -z does not scan as integer");
      break;
    case 'p':                           /* port to listen on */
      if (ipport)
        usage("service port specified multiple times (first was %hu", ipport);
      else if (sscanf(nasd_optarg, "%hu", &ipport) != 1)
        usage("argument to -p does not scan as integer");
      break;
    case 'P':                           /* default Partition to use */
      if (partition_id != -1)
        usage("partition id specified multiple times (first was %d)",
              partition_id);
      else if (sscanf(nasd_optarg, "%u", &partition_id) != 1)
        usage("argument to -P does not scan as integer");
      break;
    case 'q':                           /* be vewy vewy Quiet */
      quietude++;
      break;
    case 'h':                           /* print usage and quit */
    default:
      usage(c == 'h'? NULL: "unrecognized option on command line");
      break;
    }
  }
  /* Compatibility with old cheops_mgr -- accept partition number on
     comand line as argument. */
  if (nasd_optind < argc) {
    if (partition_id < 0) {
      fprintf(stderr, "CHEOPS MGR: WARNING: accepting partition number as "
              "argument for compatibility -- DEPRECATED\n");
      if (sscanf(argv[nasd_optind++], "%d", &partition_id) != 1)
        usage("partition `%s' number does not scan as int", argv[nasd_optind]);
      if (nasd_optind < argc)
        fprintf(stderr, "CHEOPS MGR: WARNING: extra arguments at end of CLA "
                "ignored -- DEPRECATED\n");
    } else
      usage("partition number given as both -P and as argument");
  }

  /* Check CLA sanity */
  if (override_defaults) {
    if (partition_id < 0)
      usage("no partition number specified");
    else if (partition_id >= NASD_OD_MAXPARTS)
      usage("illegal partition number %d (must be between zero and %d)",
            partition_id, NASD_OD_MAXPARTS - 1);
  } /* else hopefully it's in the resource file or else we die later */
  if (stack_size == 0)
    stack_size = DEF_STACK_SIZE;
  if (svc_threads == 0)
    svc_threads = DEF_SVC_THREADS;
  if (ipport == 0)
    ipport = DEF_SVC_IPPORT;

  /* We're cool, now get set up */
  ret = pipe(signal_pipe);
  if (ret)
    perror_die("CHEOPS MGR: cannot create signal pipe\n");
  rc = nasd_threads_init();
  if (rc)
    nerror_die(rc, "nasd_threads_init");
  rc = nasd_mem_init();
  if (rc)
    nerror_die(rc, "nasd_mem_init");
  rc = nasd_cheops_mgr_init(resource_file, dmap_file, imap_file, partition_id,
                            def_partition_size, binding_type,
                            stripe_unit, stripe_width, raid_level, quietude,
                            clear_old_state, override_defaults);
  if (rc)
    nerror_die(rc, "nasd_cheps_mgr_init");
  nasd_cheops_mgr_signal_pid = getpid();
  rc = nasd_init_threadgroup(&nasd_cheops_mgr_master_shutdown_thread_group);
  if (rc) {
    nerror(rc, "nasd_init_threadgroup(nasd_cheops_mgr_master_shutdown_group");
    goto abort_cheops_mgr;
  }
  rc = nasd_thread_create_w_name(&nasd_cheops_mgr_shutdown_worker_thread,
                                 nasd_cheops_mgr_shutdown_worker_proc,
                                 NULL, "nasd_cheops_mgr_shutdown_worker");
  if (rc) {
    nerror(rc, "nasd_thread_create(mgr_shutdown_worker");
    goto abort_cheops_mgr;
  }
  NASD_THREADGROUP_WAIT_START(&nasd_cheops_mgr_master_shutdown_thread_group);
  if (!quietude)
    printf("CHEOPS MGR: setup RPC subsystem\n");
  rc = nasd_cheops_mgr_startup_rpc();
  if (rc) {
    nerror(rc, "nasd_cheops_mgr_startup_rpc");
    goto abort_cheops_mgr;
  }
  /* See the comments in ../../edrfs/server/nasd_edrfs_main.c re Linux,
     POSIX threads and signals.  Basically, we cannot use USR1 because
     the Linux "threads" "package" "uses" it. */
#ifndef LINUX
  signal(SIGUSR1, handle_sigint);
#endif /* LINUX */
#ifndef LINUX
  signal(SIGPWR, handle_sigint);
#endif /* LINUX */
  signal(SIGINT, handle_sigint);
  signal(SIGTERM, handle_sigint);

  rc = nasd_cheops_mgr_rpc_set_stacksize(stack_size);
  if (rc == NASD_OP_NOT_SUPPORTED)
    printf("CHEOPS MGR: setting stack size not supported\n");
  else if (rc)
    printf("CHEOPS MGR: got 0x%x (%s) setting stack size to %d\n",
                rc, nasd_error_string(rc));
  else
    printf("CHEOPS MGR: stack size set to %d\n", stack_size);
  printf("Running with pid %d\n", nasd_cheops_mgr_signal_pid);
  fflush(stdout);

  rc = nasd_cheops_mgr_rpc_listen(svc_threads, ipport);
  if (rc)
    nerror(rc, "nasd_cheops_mgr_rpc_listen(%d,%d)", svc_threads, ipport);

abort_cheops_mgr:
  if (!nasd_cheops_mgr_stopped) {
    nasd_cheops_mgr_stop_rpc();
    rc = nasd_cheops_mgr_really_shutdown();
    if (rc) {
      nerror(rc, "nasd_cheops_mgr_really_shutdown");
      CLOSEUP_PIPES();
      exit(1);
    }
  } else {
    /* Some other thread is shutting down, so let them do it */
    do {
      struct timeval tv;
      int ret;

      tv.tv_sec = 1;
      tv.tv_usec = 0;
      ret = select(1, NULL, NULL, NULL, &tv);
      if (ret) 
        break;
    } while (alldone == 0);
  }

  CLOSEUP_PIPES();
  exit(2);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
