/*
 * nasd_linux_basic.c
 *
 * Basic system support stuff
 *
 * Authors: Marc Unangst, Jim Zelenka, Sean Levy
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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_mem.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_sys.h>
#ifndef KERNEL
#include <sys/time.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#endif /* KERNEL */

int
nasd_linux_nasd_status_to_errno(
  nasd_status_t  nasd_status)
{
  /* The "XXX - dux" comments mean that under dux, we returned something
     else (EBADRPC) which doesn't exist under Linux.  EINVAL seems about
     right... --SNL */
  switch(nasd_status) {
  case NASD_SUCCESS:            return(0);
  case NASD_NO_MEM:             return(ENOMEM);
  case NASD_FAIL:               return(EIO);
  case NASD_NO_MORE_OBJECTS:    return(ENFILE);
  case NASD_NO_SPACE:           return(ENOSPC);
  case NASD_BAD_DIGEST_REQ:
  case NASD_BAD_DIGEST_REPLY:
  case NASD_BAD_NONCE:
  case NASD_BAD_KEYTYPE:
  case NASD_WEAK_COOKIE:
  case NASD_WRONG_NI:
  case NASD_WRONG_DRIVE:
  case NASD_AUTHCHECK_FAILED:   return(EACCES);
  case NASD_BAD_IDENTIFIER:
  case NASD_BAD_ATTR_SET:       return(EINVAL); /* XXX - dux */
  case NASD_OP_NOT_SUPPORTED:   return(ENOSYS);
  case NASD_BAD_TIMESPEC:       return(EINVAL); /* XXX - dux */
  case NASD_NO_FREE_IDS:        return(ENOSPC);
  case NASD_GUARD_CHECK_FAILED: return(EINVAL); /* XXX - dux */
  case NASD_BAD_IO_LIST_LEN:    return(EINVAL); /* XXX - dux */
  case NASD_BAD_IO_LIST:        return(EINVAL); /* XXX - dux */

  default: /* XXX - this should never happen */ /* I bet it does --jimz */
    return(EIO);
  }
}

static nasd_timespec_t nasd_linux_last_time = {0,0};
static nasd_timespec_t nasd_linux_uniq_time = {0,0};

void
nasd_gettime(
  nasd_timespec_t  *tm)
{
  struct timeval tv;

#ifdef KERNEL
  do_gettimeofday(&tv);
#else /* KERNEL */
  gettimeofday(&tv, NULL);
#endif /* KERNEL */

  tm->ts_sec = tv.tv_sec;
  tm->ts_nsec = tv.tv_usec*1000;
  if ((tm->ts_sec == nasd_linux_last_time.ts_sec)
    && (tm->ts_nsec == nasd_linux_last_time.ts_nsec))
  {
    nasd_linux_uniq_time.ts_nsec++;
    if (nasd_linux_uniq_time.ts_nsec >= NASD_NSEC_PER_SEC) {
      nasd_linux_uniq_time.ts_nsec -= NASD_NSEC_PER_SEC;
      nasd_linux_uniq_time.ts_sec++;
    }
    *tm = nasd_linux_uniq_time;
  }
  else {
    nasd_linux_last_time = *tm;
    nasd_linux_uniq_time = nasd_linux_last_time;
  }
}

#ifndef KERNEL

nasd_status_t
nasd_sys_init()
{
  nasd_get_clock_speed(&nasd_ctimer_ticks);

  return(NASD_SUCCESS);
}

void
nasd_sys_shutdown(void)
{
  return;
}

#endif /* !KERNEL */

#ifdef KERNEL

#include <linux/smp_lock.h>
#include <asm/semaphore.h>

struct semaphore nasd_linux_d_sem = MUTEX;

nasd_uint64 nasd_ctimer_ticks;

nasd_status_t
nasd_sys_init(void)
{
  nasd_get_clock_speed(&nasd_ctimer_ticks);
  return(NASD_SUCCESS);
}

void
nasd_sys_shutdown(void)
{
  return;
}

nasd_status_t
nasd_sys_threads_init()
{
  return NASD_SUCCESS;
}

nasd_status_t
nasd_sys_threads_init_once()
{
  return(NASD_SUCCESS);
}

void
nasd_sys_threads_shutdown()
{
  return;
}

NASD_SYS_DECLARE_STATIC_MUTEX(nasd_once_lock);

nasd_status_t
nasd_sys_mutex_init(struct semaphore *m)
{
  m->count.counter = 1;
  m->waking = 0;
  m->wait = NULL;
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_mutex_destroy(struct semaphore *m)
{
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_cond_init(
  nasd_sys_cond_t  *c)
{
  c->m = MUTEX;
  c->sem = MUTEX_LOCKED;
  c->nwaiters = 0;
  c->broadcast_counter = 0;

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_cond_destroy(
  nasd_sys_cond_t  *c)
{
  if (c->nwaiters) {
    /* still in use */
    return(NASD_FAIL);
  }

  return(NASD_SUCCESS);
}

int
_nasd_once(nasd_once_t   *once_block,
           void         (*init_routine)(void),
           char          *file,
           int            line)
{
  lock_kernel();                /* XXX FIX ME SEAN */
  down(&nasd_once_lock);
  if (once_block->done == 0) {
    once_block->done = 1;
    (*init_routine)();
  }
  up(&nasd_once_lock);
  unlock_kernel();              /* XXX FIX ME SEAN */
  return 0;
}

nasd_status_t
nasd_sys_threadattr_create(
  nasd_sys_threadattr_t  *attrp)
{
  attrp->dummy = 0;
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_threadattr_destroy(
  nasd_sys_threadattr_t  *attrp)
{
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_threadattr_setstacksize(
  nasd_sys_threadattr_t  *attrp,
  int                     size)
{
  return(NASD_OP_NOT_SUPPORTED);
}

nasd_status_t
nasd_sys_threadattr_getstacksize(
  nasd_sys_threadattr_t  *attrp,
  int                    *sizep)
{
  *sizep = KERNEL_STACK_SIZE;           /* XXX fix this */
  return(NASD_SUCCESS);
}

void
nasd_linux_sys_cond_shutdown(
  void  *cond_arg)
{
  nasd_sys_cond_t *cond;
  nasd_status_t rc;

  cond = (nasd_sys_cond_t *)cond_arg;
  rc = nasd_sys_cond_destroy(cond);
  if (rc) {
    nasd_printf("NASD: got 0x%x (%s) destroying cond 0x%lx\n",
      rc, nasd_error_string(rc), (unsigned long)cond);
  }
}

void
nasd_linux_set_thread_name(const char *name)
{
  strncpy(current->comm, name, sizeof(current->comm)-1);
}

typedef struct nasd_linux_thread_start_s {
  int (*fn)(void *);
  void *arg;
  char name[32];
} nasd_linux_thread_start_t;

static int _nasd_linux_thread_start(void *arg)
{
  nasd_linux_thread_start_t *st = (nasd_linux_thread_start_t *)arg;
  int (*fn)(void*) = NULL;
  void *real_arg = NULL;

  if (st == NULL) {
    printk(KERN_ERR "_nasd_thread_start called with NULL arg\n");
    return 0;
  }
  fn = st->fn;
  real_arg = st->arg;
  if (fn == NULL) {
    kfree(arg);
    printk(KERN_ERR "_nasd_thread_start called arg is %p but fn is NULL\n",st);
    return 0;
  }
  if (st->name[0] == '\0')
    nasd_linux_set_thread_name("nasd_thread");
  else
    nasd_linux_set_thread_name(st->name);
  kfree(arg);
  lock_kernel();
  current->session = 1;
  current->pgrp = 1;
  siginitsetinv(&current->blocked, (sigmask(SIGKILL) |
                                    sigmask(SIGINT) |
                                    sigmask(SIGQUIT)));
  current->fs->umask = 0;
  unlock_kernel();
  return fn(real_arg);
}

nasd_status_t
nasd_sys_thread_create_attr(
  nasd_sys_thread_t      *threadp,
  nasd_sys_threadattr_t  *attrp,
  nasd_threadfunc_t       func,
  nasd_threadarg_t        arg)
{
  int pid;
  nasd_linux_thread_start_t *st = NULL;

  st = (nasd_linux_thread_start_t *)kmalloc(sizeof(nasd_linux_thread_start_t),
                                            GFP_KERNEL);
  if (st == NULL)
    panic("could not malloc a nasd_thread_start_t");
  st->fn = (int (*)(void*))func;
  st->arg = arg;
  st->name[0] = '\0';
  pid = kernel_thread(_nasd_linux_thread_start, (void *)st,
                      CLONE_VM | CLONE_FS | CLONE_SIGHAND);
  if (pid < 0)
    return(NASD_FAIL);
  *threadp = pid;
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_thread_create(
  nasd_sys_thread_t  *threadp,
  nasd_threadfunc_t   func,
  nasd_threadarg_t    arg)
{
  int pid;
  nasd_linux_thread_start_t *st = NULL;

  st = (nasd_linux_thread_start_t *)kmalloc(sizeof(nasd_linux_thread_start_t),
                                            GFP_KERNEL);
  if (st == NULL)
    panic("could not malloc a nasd_thread_start_t");
  st->fn = (int (*)(void *))func;
  st->arg = arg;
  st->name[0] = '\0';
  pid = kernel_thread(_nasd_linux_thread_start, (void *)st,
                      CLONE_VM | CLONE_FS | CLONE_SIGHAND);
  if (pid < 0)
    return(NASD_FAIL);
  *threadp = pid;
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_sys_thread_create_w_name(
  nasd_sys_thread_t  *threadp,
  nasd_threadfunc_t   func,
  nasd_threadarg_t    arg,
  char               *name)
{
  int pid;
  nasd_linux_thread_start_t *st = NULL;

  st = (nasd_linux_thread_start_t *)kmalloc(sizeof(nasd_linux_thread_start_t),
                                            GFP_KERNEL);
  if (st == NULL)
    panic("could not malloc a nasd_thread_start_t");
  st->fn = (int (*)(void*))func;
  st->arg = arg;
  strncpy(st->name, name, sizeof(st->name) - 1);
  pid = kernel_thread(_nasd_linux_thread_start, (void *)st,
                      CLONE_VM | CLONE_FS | CLONE_SIGHAND);
  if (pid < 0)
    return(NASD_FAIL);
  *threadp = pid;
  return(NASD_SUCCESS);
}

spinlock_t nasd_printf_lock;            /* XXX perhaps not the right way */
static char nasd_printf_buf[NASD_LINUX_KERNEL_PRINTF_BUF_LEN];
int nasd_linux_kernel_default_log_lvl = 7; /* can be set at insmod time */
                                        /* see nasd_linux_common.c in */
                                        /* kernel_generate/linux/nasd/ */

asmlinkage int nasd_printf(const char *fmt, ...)
{
  va_list args;
  int i;
  int off;
  long flags;

  spin_lock_irqsave(&nasd_printf_lock, flags);
  va_start(args, fmt);
  if (fmt[0] && fmt[1] && fmt[2] &&
      (fmt[0] == '<') &&
      ((fmt[1] > '0') && (fmt[1] < '8')) &&
      (fmt[2] == '>'))
    off = 0;
  else {
    nasd_printf_buf[0] = '<';
    nasd_printf_buf[1] = nasd_linux_kernel_default_log_lvl + '0';
    nasd_printf_buf[2] = '>';
    off = 3;
  }
  i = vsprintf(&nasd_printf_buf[off], fmt, args);
  va_end(args);
  spin_unlock_irqrestore(&nasd_printf_lock, flags);
  if (i >= sizeof(nasd_printf_buf))
    panic("nasd printf buffer overflow"); /* the only honorable thing to do */
  printk("%s", nasd_printf_buf);
  /*spin_unlock_irqrestore(&nasd_printf_lock, flags);*/ /*or does it go here?*/
  return i;
}

#endif /* KERNEL */

#ifndef KERNEL
# if NASD_USE_GLIBC_MALLOC_TRACING > 0

/* This is under Linux with glibc 2 - mtrace and friends will trace
   memory allocations from the C library */

extern char *_mtrace_file;
extern int _mtrace_line;

void *
_nasd_linux_malloc(int sz, char *fname, int line_no)
{
  _mtrace_file = fname;
  _mtrace_line = line_no;
  return malloc(sz);
}

void
_nasd_linux_free(void *ptr, char *fname, int line_no)
{
  _mtrace_file = fname;
  _mtrace_line = line_no;
  free(ptr);
}

# endif /* NASD_USE_GLIBC_MALLOC_TRACING > 0 */
#endif /* !KERNEL */

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