/*
 * Copyright (c) 1999-2001 Caucho Technology.  All rights reserved.
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  In particular,
 * the following conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL Caucho OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.      
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <dirent.h>
/* probably system-dependent */
#include <dlfcn.h>
#include <jni.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <signal.h>

#include "cse.h"
#include "version.h"
#include "proxy.h"
#include "proxy_jni.h"

static config_t *g_config;

static int g_server_count;
static registry_t **g_nodes;
static int *g_ports;
static server_t **g_servers;

typedef struct options_t {
  char *resin_home;
  char *server_root;
  char *java_home;
  char *conf;
  char *classpath;
  char *loadpath;
  char *stdout_path;
  char *stderr_path;
  char *pid;

  char *server;

  int verbose;
  
  int start;
  int stop;
  int restart;

  JavaVMOption options[64];
  int n_options;
} options_t;

static options_t g_options;
java_t g_java;

void
cse_log(char *fmt, ...)
{
#ifdef DEBUG  
  va_list list;

  va_start(list, fmt);
  vfprintf(stderr, fmt, list);
  va_end(list);
#endif
}

void
cse_error(config_t *config, char *format, ...)
{
}

void
cse_set_socket_cleanup(int socket, void *pool)
{
}

void
cse_kill_socket_cleanup(int socket, void *pool)
{
}

int
cse_lock(void *lock)
{
  return 1;
}

void
cse_unlock(void *lock)
{
}

void *
cse_create_lock(config_t *config)
{
  return 0;
}

/**
 * Returns true if the path is an absolute path
 */
static int
is_path_absolute(char *path)
{
  return (path[0] == '/' || path[0] == '\\' ||
          (path[1] == ':' &&
           ((path[0] >= 'a' && path[0] <= 'z') ||
            (path[0] >= 'A' && path[0] <= 'Z'))));
}

/**
 * Add a jvm option
 */
static void
add_jvm_option(char *fmt, ...)
{
  char buf[8192];
  va_list args;

  va_start(args, fmt);
  vsprintf(buf, fmt, args);
  va_end(args);

  g_options.options[g_options.n_options++].optionString = strdup(buf);
}

/**
 * Bind to a port.
 *
 * @param hostname name of the host interface
 * @param port port to bind
 *
 * @return server socket on success, -1 on failure.
 */
static int
cse_bind(char *hostname, int port)
{
  struct sockaddr_in sin;
  int val = 0;
  struct hostent *hostent;
  int sock;

  memset(&sin, 0, sizeof(sin));
  
  if (hostname && *hostname && *hostname != '*') {
    hostent = gethostbyname(hostname);
    if (! hostent || ! hostent->h_addr) {
      fprintf(stderr, "can't lookup host %s\n", hostname);
      return -1;
    }

    memcpy(&sin.sin_addr, hostent->h_addr, sizeof(struct in_addr));
  }
  sin.sin_port = htons(port);

  sock = socket(AF_INET, SOCK_STREAM, 0);
  
  val = 1;
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(int)) < 0) {
    fprintf(stderr, "internal error: bad set sock opt\n");
    return -1;
  }
  
  if (bind(sock, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
    return -1;
  }

  listen(sock, 5);

  return sock;
}

static void
set_classpath()
{
  char classpath[16 * 1024];
  char def[8192];
  char path[1024];
  DIR *dir;
  struct dirent *dir_entry;

  classpath[0] = 0;
  
  if (g_options.classpath) {
    sprintf(path, "%s", g_options.classpath);
    strcat(classpath, path);
  }
  
  if (getenv("CLASSPATH")) {
    strcat(classpath, ":");
    strcat(classpath, getenv("CLASSPATH"));
  }

  /* classes from resin/classes */
  sprintf(path, ":%s/classes", g_options.resin_home);
  strcat(classpath, path);

  /* grab jars from resin/lib */
  sprintf(path, "%s/lib", g_options.resin_home);
  dir = opendir(path);
  while (dir && (dir_entry = readdir(dir))) {
    int len = strlen(dir_entry->d_name);
    
    if (! strcmp(dir_entry->d_name + len - 4, ".jar") ||
        ! strcmp(dir_entry->d_name + len - 4, ".zip")) {
      strcat(classpath, ":");
      strcat(classpath, g_options.resin_home);
      strcat(classpath, "/lib/");
      strcat(classpath, dir_entry->d_name);
    }
  }
  if (dir)
    closedir(dir);

  sprintf(path, ":%s/jre/lib/rt.jar", g_options.java_home);
  strcat(classpath, path);

  sprintf(path, ":%s/lib/tools.jar", g_options.java_home);
  strcat(classpath, path);

  /*
   * Apparently, this doesn't get added to the list
   *
  sprintf(path, "%s/jre/lib/ext", g_options.java_home);
  dir = opendir(path);
  while ((dir_entry = readdir(dir))) {
    int len = strlen(dir_entry->d_name);
    
    if (! strcmp(dir_entry->d_name + len - 4, ".jar") ||
        ! strcmp(dir_entry->d_name + len - 4, ".zip")) {
      strcat(classpath, ":");
      strcat(classpath, g_options.java_home);
      strcat(classpath, "/jre/lib/ext/");
      strcat(classpath, dir_entry->d_name);
    }
  }
  closedir(dir);
  */

  g_options.classpath = classpath;
  
  add_jvm_option("-Djava.class.path=%s", classpath);
}

/**
 * Returns true if prefix + tail is a file.
 */
static int
is_file(char *prefix, char *tail)
{
  struct stat st;
  char buf[8192];

  sprintf(buf, "%s/%s", prefix, tail);

  return ! stat(buf, &st) && S_ISREG(st.st_mode);
}

/**
 * Sets the dynamic loading library path LD_LIBRARY_PATH so the JVM classes
 * will be automatically picked up.
 *
 * @param java_home the JAVA_HOME directory
 * @param resin_home the RESIN_HOME directory
 */
void
set_library_path(char *java_home, char *resin_home)
{
  char envpath[8192];
  char newpath[8192];

  char *type;

  strcpy(newpath, resin_home);
  strcat(newpath, "/libexec:");
  
  if (is_file(java_home, "jre/bin/libjava.so")) {
    type = "classic";
    
    strcat(newpath, java_home);
    strcat(newpath, "/jre/bin:");

    strcat(newpath, java_home);
    strcat(newpath, "/jre/bin/");
    strcat(newpath, type);
    strcat(newpath, ":");
  }
  else if (is_file(java_home, "jre/lib/" CPU "/libjava.so")) {
    type = "server";
 
    strcat(newpath, java_home);
    strcat(newpath, "/jre/lib/" CPU ":");

    strcat(newpath, java_home);
    strcat(newpath, "/jre/lib/" CPU "/");
    strcat(newpath, type);
    strcat(newpath, ":");
    
    strcat(newpath, java_home);
    strcat(newpath, "/jre/lib/" CPU "/native_threads");
    strcat(newpath, ":");
    
    strcat(newpath, java_home);
    strcat(newpath, "/jre/lib/" CPU "/classic");
    strcat(newpath, ":");
  }
  else {
    fprintf(stderr, "can't use JAVA_HOME=%s\n", java_home);
    exit(1);
  }

  if (getenv("LD_LIBRARY_PATH"))
    strcat(newpath, getenv("LD_LIBRARY_PATH"));

  strcpy(envpath, "LD_LIBRARY_PATH=");
  strcat(envpath, newpath);
  putenv(strdup(envpath));
}

void
setEnvVars(char *java_home)
{
  char newpath[8192];
  char *p;
  char *buffer = newpath;

    /* Now add our locale files to the beginning of XFILESEARCHPATH */

  strcpy(buffer, "XFILESEARCHPATH=");
  strcat(buffer, java_home);
  strcat(buffer, "/jre/lib/locale/%L/%T/%N%S:");

  p = getenv("XFILESEARCHPATH");
  if (p != NULL) {
    strcat(buffer, p);
  }
    
  putenv(strdup(buffer));
}

int cse_test() { return 666; }

static int
create_vm(JavaVM **p_vm, void **env, JavaVMInitArgs *vm_args)
{
  void *handle;
  
  int (*p_create_vm) (JavaVM **, void **, JavaVMInitArgs *);
  
  handle = dlopen("libjvm.so", RTLD_LAZY);
  if (! handle) {
    fprintf(stderr, "%s\n", dlerror());
    exit(1);
  }

  p_create_vm = dlsym(handle, "JNI_CreateJavaVM");
  if (! p_create_vm) {
    fprintf(stderr, "can't find JNI_CreateJavaVM\n");
    exit(1);
  }

  return p_create_vm(p_vm, env, vm_args);
}

/**
 * Parses the command-line arguments
 */
static char **
parse_command_line(int argc, char **argv)
{
  char **new_argv = (char **) malloc((argc + 1) * sizeof(char *));
  int i;
  int j = 0;

  g_options.stdout_path = "log/stdout.log";
  g_options.stderr_path = "log/stderr.log";
  
  for (i = 1; i < argc; i++) {
    if (! strcmp(argv[i], "-java_home") ||
        ! strcmp(argv[i], "-java-home")) {
      g_options.java_home = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-resin_home") ||
             ! strcmp(argv[i], "-resin-home")) {
      g_options.resin_home = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-server_root") ||
             ! strcmp(argv[i], "-server-root")) {
      g_options.server_root = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-conf") ||
             ! strcmp(argv[i], "-conf")) {
      new_argv[j++] = argv[i];
      new_argv[j++] = argv[i + 1];
      g_options.conf = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-classpath") ||
             ! strcmp(argv[i], "-cp")) {
      g_options.classpath = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-loadpath") ||
             ! strcmp(argv[i], "-lp")) {
      g_options.loadpath = strdup(argv[i + 1]);
      i++;
    }
    else if (! strncmp(argv[i], "-D", 2) ||
             ! strncmp(argv[i], "-X", 2)) {
      add_jvm_option("%s", argv[i]);
    }
    else if (! strncmp(argv[i], "-J", 2)) {

      if (! strcmp(argv[i] + 2, "-verbosegc"))
        add_jvm_option("-verbose:gc", "");
      else
        add_jvm_option("%s", argv[i] + 2);
    }
    else if (! strcmp(argv[i], "-verbosegc")) {
      add_jvm_option("-verbose:gc", "");
    }
    else if (! strcmp(argv[i], "-stdout")) {
      new_argv[j++] = argv[i];
      new_argv[j++] = argv[i + 1];
      
      g_options.stdout_path = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-stderr")) {
      new_argv[j++] = argv[i];
      new_argv[j++] = argv[i + 1];
      
      g_options.stderr_path = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-pid")) {
      g_options.pid = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-server")) {
      new_argv[j++] = argv[i];
      new_argv[j++] = argv[i + 1];
      g_options.server = strdup(argv[i + 1]);
      i++;
    }
    else if (! strcmp(argv[i], "-verbose")) {
      g_options.verbose = 1;
    }
    else if (! strcmp(argv[i], "start")) {
      g_options.start = 1;
    }
    else if (! strcmp(argv[i], "stop")) {
      g_options.stop = 1;
    }
    else if (! strcmp(argv[i], "restart")) {
      g_options.restart = 1;
    }
    else
      new_argv[j++] = argv[i];
  }

  new_argv[j] = 0;

  return new_argv;
}

/**
 * unwinds a linked path, returning the original directory.
 */
static char *
unwind_link(char *path, char *test_tail)
{
  struct stat st;
  int tail = 0;

  path = strdup(path);

  while ((! lstat(path, &st)) && S_ISLNK(st.st_mode)) {
    char buf[8192];

    readlink(path, buf, sizeof(buf));

    path = strdup(buf);
  }

  for (tail = strlen(path); tail >= 0; tail--) {
    if (path[tail] == '/') {
      path[tail] = 0;

      if (is_file(path, test_tail)) {
        return path;
      }
    }
  }

  return 0;
}

/**
 * Finds a link in a classpath-like path
 */
static char *
find_link_in_path(char *path, char *test_tail, char *exe)
{
  char *head;
  char *tail = 0;

  for (head = path; head && *head; head = tail) {
    char buf[8192];
    struct stat st;
      
    tail = strchr(head, ':');

    if (tail) {
      *tail = 0;
      tail++;
    }

    sprintf(buf, "%s/%s", head, exe);
      
    if (! stat(buf, &st)) {
      char *dir = unwind_link(buf, test_tail);

      if (dir)
        return dir;
    }
  }

  return 0;
}

/**
 * Finds the value of java_home
 *
 * 1) command-line
 * 2) getenv("JAVA_HOME")
 * 3) find by searching back the "java" command
 * 4) trying /usr/java
 */
static void
find_java_home()
{
  char *java_home;
  char *path;
  
  if (g_options.java_home)
    return;

  if ((java_home = getenv("JAVA_HOME"))) {
    g_options.java_home = strdup(java_home);
    return;
  }

  path = getenv("PATH");
  if (path) {
    path = strdup(path);
    
    g_options.java_home = find_link_in_path(path, "jre/lib/rt.jar", "java");
  }

  if (! g_options.java_home)
    g_options.java_home = "/usr/java";
}

/**
 * Finds the value of resin_home
 *
 * 1) command-line
 * 2) getenv("RESIN_HOME")
 * 3) trying /usr/local/resin
 */
static void
find_resin_home(char *cmd)
{
  char *resin_home;
  char *path;
  
  if (g_options.resin_home) {
    return;
  }

  if ((resin_home = getenv("RESIN_HOME"))) {
    g_options.resin_home = strdup(resin_home);
    return;
  }

  g_options.resin_home = unwind_link(cmd, "lib/dom.jar");
  if (g_options.resin_home)
    return;

  if (! g_options.resin_home) {
    char buf[8192];
    getcwd(buf, sizeof(buf));

    g_options.resin_home = strdup(buf);
  }
}

/**
 * Convert a possibly relative path to an absolute path.
 */
static char *
normalize_path(char *path)
{
  char buf[8192];

  if (path[0] == '/')
    return path;

  getcwd(buf, sizeof(buf));
  strcat(buf, "/");
  strcat(buf, path);

  return strdup(buf);
}

static void
init_java(JNIEnv *env)
{
  jclass classID;
  jmethodID methodID;
  jfieldID fieldID;
  
  jni_vfs_init(env);

  classID = (*env)->FindClass(env, "com/caucho/server/http/JniServer");
  if (classID == 0) {
    fprintf(stderr, "Class not found: com.caucho.server.http.JniServer\n");
    exit(1);
  }
  g_java.jni_server = classID;

  classID = (*env)->FindClass(env, "com/caucho/server/http/JniRequest");
  if (classID == 0) {
    fprintf(stderr, "Class not found: com.caucho.server.http.JniRequest\n");
    exit(1);
  }
  g_java.jni_request = classID;

  /* Get the application's main method */
  methodID = (*env)->GetMethodID(env, g_java.jni_server, "<init>",
                                 "([Ljava/lang/String;)V");
  if (! methodID) {
    fprintf(stderr, "Can't instantiate com.caucho.server.http.JniServer\n");
    exit(1);
  }
  g_java.new_jni_server = methodID;
  
  methodID = (*env)->GetMethodID(env, g_java.jni_server, "createRequest",
                                 "()Lcom/caucho/server/http/JniRequest;");
  if (! methodID) {
    fprintf(stderr, "Can't find createRequest");
    exit(1);
  }
  g_java.create_request = methodID;
  
  methodID = (*env)->GetMethodID(env, g_java.jni_server, "close", "()V");
  if (! methodID) {
    fprintf(stderr, "Can't find close");
    exit(1);
  }
  g_java.close = methodID;
  
  methodID = (*env)->GetMethodID(env, g_java.jni_request,
                                 "dispatch", "(IIIIII)Z");
  g_java.dispatch = methodID;

  fieldID = (*env)->GetFieldID(env, g_java.jni_request, "uri", "[B");
  g_java.f_uri = fieldID;
  
  fieldID = (*env)->GetFieldID(env, g_java.jni_request, "uriLength", "I");
  g_java.f_uri_length = fieldID;
  
  fieldID = (*env)->GetFieldID(env, g_java.jni_request, "headerBuffer", "[C");
  g_java.f_header_buffer = fieldID;
  
  fieldID = (*env)->GetFieldID(env, g_java.jni_request, "headerOffsets", "[I");
  g_java.f_header_offsets = fieldID;
  
  fieldID = (*env)->GetFieldID(env, g_java.jni_request, "headerSize", "I");
  g_java.f_header_size = fieldID;
}

/**
 * Reads the configuration file
 *
 * @param name the configuration file name, if specified on the command-line.
 */
config_t *
read_config(char *name)
{
  char config_file[8192];
  FILE *file;
  config_t *config = cse_malloc(sizeof(config_t));
  
  memset(config, 0, sizeof(config_t));

  config->p = cse_create_pool(config);
  config->resin_home = g_options.server_root;

  if (! name)
    name = "conf/resin.conf";

  file = fopen(name, "r");
  if (! file) {
    fprintf(stderr, "can't find config file %s\n", name);
    exit(1);
  }

  config->registry = cse_parse(file, config, name);

  fclose(file);

  return config;
}

/**
 * Binds the appropriate ports
 *
 * @param node the port's configuration node.
 *
 * @return the filedescriptor for the bound port
 */
static int
bind_port(registry_t *node)
{
  char *value;
  char *host;
  int port;
  int ss;

  if (! node) {
    fprintf(stderr, "no <http> server entries defined.\n");
    exit(1);
  }

  host = cse_find_value(node->first, "host");
  if (! host)
    host = "*";
  
  value = cse_find_value(node->first, "port");
  if (value)
    port = atoi(value);
  else
    port = 8080;
  
  ss = cse_bind(host, port);
  if (ss < 0) {
    fprintf(stderr,
            "Can't bind to port %s:%d.  Check for conflicting servers\n",
            host, port);
    exit(1);
  }

  if (cse_next_link(node->first, "ssl"))
    fprintf(stdout, "https listening to %s:%d\n", host, port);
  else
    fprintf(stdout, "http listening to %s:%d\n", host, port);

  return ss;
}

/**
 * Count the number of ports to bind.
 */
static int
count_ports(registry_t *node, char *server)
{
  char *value;
  char *host;
  int port;
  int ss;
  int count = 0;

  if (node)
    node = cse_next_link(node->first, "caucho.com");
  if (node)
    node = cse_next_link(node->first, "http-server");

  if (node)
    node = cse_next_link(node->first, "http");

  for (; node; node = cse_next_link(node->next, "http")) {
    if (server && (! node->value || strcmp(server, node->value)))
      continue;

    count++;
  }

  return count;
}

/**
 * Binds the appropriate ports
 *
 * @param node the port's configuration node.
 *
 * @return the filedescriptor for the bound port
 */
static registry_t *
find_port(registry_t *node, char *server, int index)
{
  char *value;
  char *host;
  int port;
  int ss;
  int count = index;

  if (node)
    node = cse_next_link(node->first, "caucho.com");
  if (node)
    node = cse_next_link(node->first, "http-server");

  if (node)
    node = cse_next_link(node->first, "http");

  for (; node; node = cse_next_link(node->next, "http")) {
    if (server && (! node->value || strcmp(server, node->value)))
      continue;
    else if (--count < 0)
      break;
  }

  return node;
}

/**
 * If necessary, sets the user id.
 *
 * @param node top-level configuration node.
 */
static void
set_user_id(registry_t *node)
{
  char *user_name = 0;
  char *group_name = 0;
  registry_t *subnode = 0;
  struct passwd *passwd;
  struct group *group;
  int uid;
  int gid;
  
  if (node)
    node = cse_next_link(node->first, "caucho.com");

  if (node)
    subnode = cse_next_link(node->first, "http-server");

  if (node)
    user_name = cse_find_value(node->first, "user-name");
  
  if (! user_name && subnode)
    user_name = cse_find_value(subnode->first, "user-name");
  
  if (node)
    group_name = cse_find_value(node->first, "group-name");
  
  if (! user_name && subnode)
    group_name = cse_find_value(subnode->first, "group-name");

  if (! user_name)
    return;

  passwd = getpwnam(user_name);
  
  if (passwd) {
    uid = passwd->pw_uid;
    gid = passwd->pw_gid;
  }
  else {
    fprintf(stderr, "Can't change to unknown user %s\n", user_name);
    exit(1);
  }

  if (group_name) {
    group = getgrnam(group_name);
    if (group)
      gid = group->gr_gid;
  }

  if (uid >= 0) {
    if (gid >= 0) {
      setgid(gid);
      initgroups(user_name, gid);
    }

    if (setuid(uid) < 0) {
      fprintf(stderr, "Can't run as user %s(uid=%d)\n", user_name, uid);
      exit(1);
    }
  }
  
  fprintf(stderr, "Running as %s(uid=%d)\n", user_name, uid);
}

/**
 * Creates a new JniServer object
 *
 * @param env the JVM environment
 * @param argc the command-line argc
 * @param argv the comment-line argv
 *
 * @return the JniServer handle
 */
static jobject
create_server(JNIEnv *env, char **argv)
{
  jobject server_obj = 0;
  jarray java_args;
  jclass string_class;
  int i;
  int argc;

  for (argc = 0; argv[argc]; argc++) {
  }

  string_class = (*env)->FindClass(env, "java/lang/String");
  
  java_args = (*env)->NewObjectArray(env, argc, string_class, 0);
  for (i = 0; i < argc; i++) {
    jstring arg = (*env)->NewStringUTF(env, argv[i]);
    
    (*env)->SetObjectArrayElement(env, java_args, i, arg);
  }
  
  server_obj = (*env)->NewObject(env, g_java.jni_server,
                                 g_java.new_jni_server, java_args);
  if ((*env)->ExceptionOccurred(env)) {
    (*env)->ExceptionDescribe(env);
    exit(1);
  }
  server_obj = (*env)->NewGlobalRef(env, server_obj);

  return server_obj;
}

/**
 * Forks the server for the background server.
 */
static void
fork_server(int argc, char **argv)
{
  char *pid = "resin";
  char pid_file[1024];
  int fd;
  int pipes[2];
  FILE *file;
  int child_pid;
  struct stat st;
  int status;
  char **new_argv;
  int i, j;

  if (g_options.pid)
    pid = g_options.pid;
  
  sprintf(pid_file, "%s/%s.pid", g_options.server_root, pid);

  if (! stat(pid_file, &st)) {
    fprintf(stderr, "server %s.pid has already started.\n", pid);
    exit(1);
  }

  /* become a daemon */
  if (fork()) {
    exit(0);
  }

  if (fork()) {
    exit(0);
  }
  
  setpgid(0, 0);
  
  file = fopen(pid_file, "w+");
  fprintf(file, "%d\n", getpid());
  fclose(file);

  new_argv = (char **) malloc((argc + 10) * sizeof(char *));
  j = 0;
  for (i = 0; i < argc; i++) {
    /* cleanup if necessary */
    new_argv[j++] = argv[i];
  }
  new_argv[j++] = "-stdout";
  new_argv[j++] = g_options.stdout_path;
  new_argv[j++] = "-stderr";
  new_argv[j++] = g_options.stderr_path;
  new_argv[j] = 0;

  while (1) {
    /*
     * Pipe for the deadwait.  When the parent closes, the child
     * will automatically close (see the tail of main()).
     */
    pipe(pipes);

    child_pid = fork();

    if (! child_pid) {
      char *exe = new_argv[0];
    
      dup2(pipes[0], 0);
      close(pipes[0]);
      close(pipes[1]);

      execv(exe, new_argv);

      fprintf(stderr, "can't start %s\n", new_argv[0]);
      exit(1);
    }

    close(pipes[0]);

    wait(&status);

    close(pipes[1]);

    kill(child_pid, SIGQUIT);

    sleep(10);
  }
}

static void
stop_server()
{
  char *pid = "resin.pid";
  char pid_file[1024];
  struct stat st;
  FILE *file;
  int child_pid;

  if (g_options.pid)
    pid = g_options.pid;
  
  sprintf(pid_file, "%s/%s", g_options.server_root, pid);

  if (stat(pid_file, &st)) {
    fprintf(stderr, "no server %s has been started.\n", pid);
    exit(1);
  }

  file = fopen(pid_file, "r");
  fscanf(file, "%d", &child_pid);
  fclose(file);

  unlink(pid_file);

  fprintf(stderr, "shutting down client %d\n", child_pid);
  
  if (child_pid > 0)
    kill(child_pid, 15);

  exit(0);
}

void
resin_close_server(int fd)
{
  int i;

  for (i = 0; i < g_server_count; i++) {
    if (g_ports[i] == fd) {
      close_server(g_servers[i]);
      return;
    }
  }

  if (g_server_count > 0)
    close_server(g_servers[0]);
}

void
resin_close_all()
{
  int i;
  
  for (i = 0; i < g_server_count; i++)
    close_server(g_servers[i]);
}

static void
open_logs()
{
  char log_path[1024];
  int fd;

  sprintf(log_path, "%s/log", g_options.server_root);
  mkdir(log_path, 0775);

  if (is_path_absolute(g_options.stdout_path))
    sprintf(log_path, "%s", g_options.stdout_path);
  else
    sprintf(log_path, "%s/%s", g_options.server_root, g_options.stdout_path);
  fd = open(log_path, O_RDWR|O_CREAT|O_APPEND, 0664);
  
  dup2(fd, 1);
  close(fd);

  if (is_path_absolute(g_options.stderr_path))
    sprintf(log_path, "%s", g_options.stderr_path);
  else
    sprintf(log_path, "%s/%s", g_options.server_root, g_options.stderr_path);
  fd = open(log_path, O_RDWR|O_CREAT|O_APPEND, 0664);
  dup2(fd, 2);
  close(fd);
}

int
main(int argc, char **argv)
{
  int ss;

  JavaVM *vm = 0;
  JNIEnv *env = 0;
  JavaVMInitArgs vm_args;
  jobject server_obj = 0;
  char envpath[8192];
  char buf[16];
  int len;
  int res;
  config_t *config;
  char **new_argv;
  int i;
  int result;

  memset(&g_options, 0, sizeof(g_options));
  memset(&vm_args, 0, sizeof(vm_args));
  
  new_argv = parse_command_line(argc, argv);
  
  find_java_home();
  find_resin_home(argv[0]);
  g_options.resin_home = normalize_path(g_options.resin_home);
  
  if (! g_options.server_root)
    g_options.server_root = g_options.resin_home;
  else
    g_options.server_root = normalize_path(g_options.server_root);

  if (g_options.stop || g_options.restart) {
    stop_server();
  }

  /*
   * First pass through needs to set the LD_LIBRARY_PATH and reexecute
   * the server.
   */
  if (! getenv("RESIN_STARTED")) {
    putenv("RESIN_STARTED=true");

    set_library_path(g_options.java_home, g_options.resin_home);

    if (g_options.start) {
      fork_server(argc, argv);
      exit(1);
    }
    else {
      execv(argv[0], argv);

      fprintf(stderr, "can't start server %s.\n", argv[0]);
      exit(1);
    }
  }

  chdir(g_options.server_root);

  setEnvVars(g_options.java_home);
  set_classpath();

  config = read_config(g_options.conf);

  g_config = config;

  fprintf(stdout, "%s\n", FULL_VERSION);

  if (g_options.verbose) {
    fprintf(stdout, "JAVA_HOME:   %s\n", g_options.java_home);
    fprintf(stdout, "RESIN_HOME:  %s\n", g_options.resin_home);
    fprintf(stdout, "SERVER_ROOT: %s\n", g_options.server_root);
    fprintf(stdout, "CLASSPATH:   %s\n", g_options.classpath);
    fprintf(stdout, "LDPATH:      %s\n", getenv("LD_LIBRARY_PATH"));

    for (i = 0; new_argv[i]; i++)
      fprintf(stdout, "arg %d:      %s\n", i, new_argv[i]);
  }

  g_server_count = count_ports(config->registry, g_options.server);
  
  if (g_server_count <= 0) {
    fprintf(stderr, "No servers defined.\n");
    exit(1);
  }
  
  g_nodes = (registry_t **) malloc(g_server_count * sizeof(registry_t *));
  memset(g_nodes, 0, g_server_count * sizeof(registry_t *));
  
  g_ports = (int *) malloc(g_server_count * sizeof(int));
  memset(g_ports, 0, g_server_count * sizeof(int));
  
  g_servers = (server_t **) malloc(g_server_count * sizeof(server_t *));
  memset(g_servers, 0, g_server_count * sizeof(server_t *));

  for (i = 0; i < g_server_count; i++) {
    registry_t *node = find_port(config->registry, g_options.server, i);
    
    ss = bind_port(node);

    if (ss < 0) {
      fprintf(stderr, "failure opening port.\n");
      exit(1);
    }
  
    g_nodes[i] = node;
    g_ports[i] = ss;
  }
  
  /* XXX: now, change ownership */
  set_user_id(config->registry);

  if (g_options.start)
    open_logs();

  cse_mem_init();
  
  strcpy(envpath, "-Djava.library.path=");
  strcat(envpath, getenv("LD_LIBRARY_PATH"));

  add_jvm_option("-Djava.library.path=%s", getenv("LD_LIBRARY_PATH"));
  add_jvm_option("-Dresin.home=%s", g_options.server_root);

  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = g_options.options;
  vm_args.nOptions = g_options.n_options;
  vm_args.ignoreUnrecognized = 0;

  res = create_vm(&vm, (void **)&env, &vm_args);

  if (! env) {
    fprintf(stderr, "Failed to create JVM.\n");
    exit(1);
  }
  
  g_java.vm = vm;

  init_java(env);

  server_obj = create_server(env, new_argv);
  
  for (i = 0; i < g_server_count; i++)
    g_servers[i] = start_server(g_nodes[i], g_ports[i], env, server_obj);

  len = 1;
  do {
    fd_set read_set;
    struct timeval timeout;
    
    result = 0;
    
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    FD_ZERO(&read_set);
    FD_SET(0, &read_set);

    if (g_options.start) {
      result = select(1, &read_set, 0, 0, &timeout);
      if (result > 0)
        break;
    }
    else
      result = select(1, 0, 0, 0, &timeout);

    for (i = g_server_count - 1; i >= 0; i--) {
      if (server_is_dead(g_servers[i]))
        break;
    }
  } while (i < 0 && (result >= 0 || result < 0 && errno == EINTR));

  LOG(("closing\n"));
  
  for (i = g_server_count - 1; i >= 0; i--)
    close_server(g_servers[i]);
  
  (*env)->CallVoidMethod(env, server_obj, g_java.close);
  
  if ((*env)->ExceptionOccurred(env))
    (*env)->ExceptionDescribe(env);
  
  (*vm)->DetachCurrentThread(vm);

  (*vm)->DestroyJavaVM(vm);


  return 0;
}
