/*
 * nasd_edrfs_main.c
 *
 * main() and friends for NASD EDRFS server.
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_getopt.h>
#include <nasd/nasd_edrfs_mq.h>
#include <nasd/nasd_edrfs_server_internal.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_edrfs_server.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_sys.h>

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *progname;
char *mountfile = "./nasd_edrfs_mounts";

int svc_threads = 15;
int stack_size  = 32768;

int                           binding_type;
int                           binding_args_len;
void                         *binding_args;
char                         *binding_port = NASD_PDRIVE_PORT;
nasd_drive_param_kpdev_t      kpdev_args;
int                           nondefault_binding = 0;

/* 
 * Under Linux, signals may be delivered to *all* of the threads, not
 * just one (as under DUX).  This is a violation of the POSIX pthreads
 * spec, and happens because Linux "threads" are really "processes"
 * that happen to share the same address space and file descriptor
 * set.  Signals sent with kill() or raise() will go to the thread
 * whose PID is specified (which also violates POSIX, but let's not go
 * there), but signals sent to the process group -- say, when you press
 * ^C on a tty -- go to all threads in the process group.
 * 
 * Because it is impossible to determine which thread will hit this
 * code first, it is possible for the thread that runs the shutdown
 * code to be a thread that also has cleanup work to do before it
 * exits.  If that happens, the drive will deadlock.  Therefore, we
 * create a "supervisor" thread to do the shutdown stuff, and allow
 * the thread that ran the signal handler to return.  When signals come
 * in, we simply ensure that this thread awakens.
 *
 * Under other systems, we're going to go ahead and create this extra
 * thread as well so we can avoid tricky synchronization at cleanup
 * time.
 */
nasd_thread_id_t nasd_edrfs_fm_master_shutdown_thread_id;
nasd_threadgroup_t nasd_edrfs_fm_master_shutdown_thread_group;
nasd_thread_t nasd_edrfs_fm_shutdown_worker_thread;
sig_atomic_t nasd_edrfs_fm_shutdown_signalled = 0;
int alldone = 0;

int nasd_edrfs_fm_stopped = 0;

pid_t nasd_edrfs_fm_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);

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

  nasd_edrfs_fm_signal_pid = getpid();

  nasd_edrfs_fm_master_shutdown_thread_id = nasd_thread_self();
  NASD_THREADGROUP_RUNNING(&nasd_edrfs_fm_master_shutdown_thread_group);

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

  while(!nasd_edrfs_fm_shutdown_signalled) {
    rdb = mrdb;
    ret = select(signal_pipe_rd+1, &rdb, NULL, NULL, NULL);
    if (nasd_edrfs_fm_shutdown_signalled || FD_ISSET(signal_pipe_rd, &mrdb))
      break;
    if (ret) {
      fprintf(stderr, "EDRFS SERVER 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_edrfs_fm_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_edrfs_stop_rpc();

#if MJU_DEBUG
  fprintf(stderr, "shutdown[%lu]: nasd_edrfs_stop_rpc() complete\n", self);
  fflush(stderr);
#endif /* MJU_DEBUG */

  rc = nasd_edrfs_really_shutdown();
  if (rc) {
    fprintf(stderr,
      "EDRFS ERROR: 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);
}

nasd_status_t
nasd_edrfs_do_drive_bind(
  char                 *bind_name,
  nasd_drive_handle_t  *dhp)
{
  nasd_status_t rc;

  rc = nasd_bind_to_drive(bind_name, binding_port,
    binding_type, binding_args, binding_args_len, dhp);
  return(rc);
}

nasd_status_t
nasd_edrfs_frame_init()
{
  /* no longer any user-specific stuff to init here */
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_edrfs_load_mounts()
{
  char buf[1024], *path, *drive, *part;
  int p, line, nmounts, ret, portnum;
  nasd_uint32 net_addr;
  struct hostent *he;
  nasd_status_t rc;
  FILE *f;

  ret = sscanf(NASD_PDRIVE_PORT, "%d", &portnum);
  if (ret != 1) {
    NASD_PANIC();
  }
  f = fopen(mountfile, "r");
  if (f == NULL) {
    fprintf(stderr, "EDRFS: Could not open %s for reading\n", mountfile);
    fflush(stderr);
    return(NASD_FAIL);
  }

  nmounts = 0;
  line = 0;
  while(fgets(buf, 1024, f) != NULL) {
    line++;
    buf[1023] = '\0';
    if ((buf[0] == '\0') || (buf[0] == '#'))
      continue;
    path = strtok(buf, " \t\n");
    if (path == NULL)
      continue;
    drive = strtok(NULL, " \t\n");
    if (drive == NULL) {
      fprintf(stderr, "Error parsing %s at line %d: no drive name\n",
        mountfile, line);
      fflush(stderr);
      exit(1);
    }
    part = strtok(NULL, " \t\n");
    if (part == NULL) {
      fprintf(stderr, "Error parsing %s at line %d: no partition number\n",
        mountfile, line);
      fflush(stderr);
      exit(1);
    }
    if (sscanf(part, "%d", &p) != 1) {
      fprintf(stderr, "Error parsing %s at line %d: bad partition number\n",
        mountfile, line);
      fflush(stderr);
      exit(1);
    }
    if ((p < 0) || (p > 64)) {
      fprintf(stderr, "Error parsing %s at line %d: bad partition number\n",
        mountfile, line);
      fflush(stderr);
      exit(1);
    }
    he = gethostbyname(drive);
    if (he == NULL) {
      fprintf(stderr, "Error resolving hostname %s (file %s line %d)\n",
        drive, mountfile, line);
      fflush(stderr);
      exit(1);
    }
    bcopy(he->h_addr_list[0], (char *)&net_addr, 4);
    rc = nasd_edrfs_add_mount(path, drive, drive, portnum, p, net_addr);
    if (rc) {
      fclose(f);
      return(rc);
    }
    nmounts++;
  }
  fclose(f);
  if (nmounts == 0) {
    fprintf(stderr, "EDRFS WARNING: no mounts found in %s\n", mountfile);
    fflush(stderr);
  }
  return(NASD_SUCCESS);
}

void
usage()
{
  fprintf(stderr, "USAGE: %s [options]\n", progname);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "  -k use kernel device\n");
  fprintf(stderr, "  -l use colocated drive\n");
  fprintf(stderr, "  -m mountfile [default %s]\n", mountfile);
  fprintf(stderr, "  -s stack size [default %d]\n", stack_size);
  fprintf(stderr, "  -t service threads [default %d]\n", svc_threads);
  fprintf(stderr, "  -x ignore preexisting message queue\n");
  fflush(stderr);
  exit(1);
}

int
main(
  int     argc,
  char  **argv)
{
  nasd_uint16 ipport;
  struct timeval tv;
  nasd_status_t rc;
  char c, *env;
  int ret;

  progname = argv[0];

  binding_type = NASD_BIND_DEFAULT;
  binding_args = NULL;
  binding_args_len = 0;

  env = getenv("NASD_EDRFS_MNTFILE");
  if (env) {
    mountfile = env;
  }

  while (nasd_getopt(argc, argv, "klm:s:t:x", &c)) {
    switch(c) {
      case 'k':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_KPDEV_DEFAULT;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'l':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_COLOCATE;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'm':
        mountfile = nasd_optarg;
        break;
      case 's':
        if (sscanf(nasd_optarg, "%d", &stack_size) != 1)
          usage();
        break;
      case 't':
        if (sscanf(nasd_optarg, "%d", &svc_threads) != 1)
          usage();
        break;
      case 'x':
        nasd_edrfs_mq_ignore_eexist = 1;
        break;
      default:
        fprintf(stderr, "Unknown option '%c'\n", c);
        usage();
    }
  }
  if (nasd_optind < argc)
    usage();

  ret = pipe(signal_pipe);
  if (ret) {
    fprintf(stderr, "ERROR: cannot create signal pipe\n");
    fflush(stderr);
    exit(1);
  }

  rc = nasd_threads_init();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_threads_init()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  rc = nasd_mem_init();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_mem_init()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  rc = nasd_edrfs_init();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_edrfs_init()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  rc = nasd_edrfs_load_mounts();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_edrfs_load_mounts()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  rc = nasd_edrfs_mq_specific_init();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from "
            "nasd_edrfs_mq_specific_init()\n", rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  nasd_edrfs_fm_signal_pid = getpid();

  rc = nasd_init_threadgroup(&nasd_edrfs_fm_master_shutdown_thread_group);
  if (rc) {
    fprintf(stderr,
      "ERROR: got 0x%x (%s) initializing "
      "nasd_edrfs_fm_master_shutdown_thread_group\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    goto abort_edrfs_fm;
  }

  rc = nasd_thread_create_w_name(&nasd_edrfs_fm_shutdown_worker_thread,
    nasd_edrfs_fm_shutdown_worker_proc, NULL, "nasd_edrfs_fm_shutdown_worker");
  if (rc) {
    fprintf(stderr,
      "ERROR: got 0x%x (%s) creating shutdown worker thread\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    goto abort_edrfs_fm;
  }
  NASD_THREADGROUP_WAIT_START(&nasd_edrfs_fm_master_shutdown_thread_group);

  printf("EDRFS: setup RPC subsystem\n");

  if (sscanf(NASD_EDRFS_SERVER_PORT, "%hu", &ipport) != 1) {
    NASD_PANIC();
  }
 
  rc = nasd_edrfs_startup_rpc();
  if (rc) {
    fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_edrfs_startup_rpc()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    CLOSEUP_PIPES();
    exit(1);
  }

#ifndef LINUX
  signal(SIGUSR1, handle_sigint);
#endif /* LINUX */

#ifdef LINUX
  /* Linux's pthreads library uses SIGUSR1 and SIGUSR2, so we can't. */
  signal(SIGPWR, handle_sigint);
#endif /* LINUX */

  signal(SIGINT, handle_sigint);

  rc = nasd_edrfs_rpc_set_stacksize(stack_size);
  if (rc == NASD_OP_NOT_SUPPORTED) {
    printf("EDRFS: setting stack size not supported\n");
  }
  else if (rc) {
    printf("EDRFS: got 0x%x (%s) setting stack size to %d\n",
           rc, nasd_error_string(rc), stack_size);
  }
  else {
    printf("EDRFS: stack size set to %d\n", stack_size);
  }

  printf("Running with pid %d\n", nasd_edrfs_fm_signal_pid);
  fflush(stdout);

  rc = nasd_edrfs_mq_specific_listen(svc_threads);
  if(rc) {
    fprintf(stderr, "Couldn't start message queue listener: status=0x%x (%s)\n",
            rc, nasd_error_string(rc));
  } else {
    rc = nasd_edrfs_rpc_listen(svc_threads, ipport);
    if (rc) {
      fprintf(stderr, "EDRFS ERROR: got 0x%x (%s) from nasd_edrfs_rpc_listen()\n",
              rc, nasd_error_string(rc));
      fflush(stderr);
    }
  }

abort_edrfs_fm:
  if (!nasd_edrfs_fm_stopped) {
    nasd_edrfs_stop_rpc();

    rc = nasd_edrfs_really_shutdown();
    if (rc) {
      fprintf(stderr, "ERROR: got %s (%lx) from nasd_edrfs_really_shutdown()\n",
        nasd_error_string(rc), (u_long)rc);
      fflush(stderr);
      CLOSEUP_PIPES();
      exit(1);
    }
  }
  else {
    /*
     * Someone else is stopping. They'll exit for us.
     */
    do {
      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: */
