Source

ocaml-toplevel-android / jni / start.c

#include "jp_co_itpl_ocamlandroid_Native.h"
#include "caml/callback.h"


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SERVER     "jp.co.itpl.ocamltop.stdinout"
#define SERVER_ERR "jp.co.itpl.ocamltop.stderr"

/*
 * Create a UNIX-domain socket address in the Linux "abstract namespace".
 *
 * The socket code doesn't require null termination on the filename, but
 * we do it anyway so string functions work.
 */
static int makeAddr(const char* name, struct sockaddr_un* pAddr, socklen_t* pSockLen)
{
    int nameLen = strlen(name);
    if (nameLen >= (int) sizeof(pAddr->sun_path) -1)  /* too long? */
        return -1;
    pAddr->sun_path[0] = '\0';  /* abstract namespace */
    strcpy(pAddr->sun_path+1, name);
    pAddr->sun_family = AF_LOCAL;
    *pSockLen = 1 + nameLen + offsetof(struct sockaddr_un, sun_path);
    return 0;
}

static int waitfor(const char* name) {

  struct sockaddr_un server_addr;
  socklen_t server_addr_len;

  if (makeAddr(name, &server_addr, &server_addr_len) < 0)
      return 0;

  int listenfd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
  if (listenfd < 0) {
      return 0;
  }

  int serverfd = 0;
  if (bind(listenfd, (const struct sockaddr*) &server_addr, server_addr_len) < 0) {
      perror("server bind()");
      goto bail;
  }
  if (listen(listenfd, 5) < 0) {
      perror("server listen()");
      goto bail;
  }
  serverfd = accept(listenfd, NULL, NULL);
  if (serverfd < 0) {
      perror("server accept");
      serverfd = 0;
      goto bail;
  }

bail:
  if(listenfd>0) close(listenfd);

  return serverfd;
}

static int server_inout = 0;
static int server_err = 0;

static int redirect_start() {

  server_err = waitfor(SERVER_ERR);
  if(server_err==0) return 0;
  server_inout = waitfor(SERVER);
  if(server_inout==0) return 0;

  dup2(server_inout, 0); // stdin  
  dup2(server_inout, 1); // stdout
  dup2(server_err, 2); // stderr

  return 1;
}

static void redirect_end() {
  // TODO dup2で変えたfdを元に戻す
  if(server_err!=0) close(server_inout);
  if(server_inout!=0) close(server_inout);
}

static void chdir_jstring(JNIEnv * env, jstring path_) {
  const jbyte *path = (*env)->GetStringUTFChars(env, path_, NULL);
  if (path == NULL) {
      return;
  }
  chdir(path);
  (*env)->ReleaseStringUTFChars(env, path_, path);
}

JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_start (JNIEnv * env, jclass clazz, jstring path) {

  chdir_jstring(env, path);
  
  if(!redirect_start()) {
    redirect_end(); 
    return;
  }

  char* p = 0;
  caml_startup(&p);

  redirect_end();
  
}