Launching bash as sub process from Java program

Java Linux
Recently one of my colleague who was attempting to mimic the 'host' command found in Oracle's 'sql*plus' (written in C) to EnterpriseDB's 'edb*plus' which is written in Java.

Both sql*plus and edb*plus are non-gui, shell based programs.

The 'host' command launches a new bash shell over top of current running sql*plus process. User use this new bash shell and when they type 'exit', the prompt again returns to to sql*plus.

This become quite a challenge to implement in Java. The typical approach that both my colleague and I come up with initially was to launch bash as
Runtime.exec("bash")
or
Runtime.exec("bash -i")
and use the process' inputstream and outputstream to communicate with bash. These approaches, however, didn't get us the bash prompt, that is we were not able to detach current running java program and introduce a new bash prompt.

So, i finally come up with following solution which seem to do the trick. The trick is to go native. In our native code, we need to fork bash as a separate process and then wait for it. This is technically different from what Runtime.exec does. Runtime.exec technically calls bash in same process space and pass the arguments of exec to bash itself. Going native actually spawns a new process from JVM process itself. Here is the code:

TestBash.java:

import java.io.*;

public class TestBash {
public static native void gonative();

public static void main(String args[]) throws IOException {
System.loadLibrary("gonativelib");

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.print("edb*plus:");
String line = br.readLine();
if(line == null) line="";
if(line.equalsIgnoreCase("host")) {
gonative();
}else if(line.equalsIgnoreCase("quit")) {
break;
}else {
System.err.println("valid commands: host, quit");
}
}
System.exit(0);
}

}


gonative.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class TestBash */

#ifndef _Included_TestBash
#define _Included_TestBash
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestBash
* Method: gonative
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_TestBash_gonative
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif


gonativelib.c:

#include
#include
#include
#include
#include
#include "gonative.h"
/* Spawn a child process running a new program. PROGRAM is the name
* of the program to run; the path will be searched for this program.
* ARG_LIST is a NULL-terminated list of character strings to be
* passed as the programu00eds argument list. Returns the process ID of
* the spawned process. */
int spawn (char* program, char** arg_list)
{
pid_t child_pid;
/* Duplicate this process. */
child_pid = fork ();
if (child_pid != 0)
/* This is the parent process. */
return child_pid;
else {
/* Now execute PROGRAM, searching for it in the path. */
execvp (program, arg_list);
/* The execvp function returns only if an error occurs. */
fprintf (stderr, "an error occurred in execvp\n");
abort ();
}
}
/*
* Native Method which will be called from Java class
*/
JNIEXPORT void JNICALL Java_TestBash_gonative (JNIEnv *env, jclass classname)
{
int child_status;
/* The argument list to pass to the "ls" command. */
char* arg_list[] = {
"bash", /* argv[0], the name of the program. */
NULL /* The argument list must end with a NULL. */
};
/* Spawn a child process running the "ls" command. Ignore the
* returned child process ID. */
spawn ("bash", arg_list);
/* Wait for the child process to complete. */
wait (&child_status);
/*
if (WIFEXITED (child_status))
printf ("the child process exited normally, with exit code %d\n",
WEXITSTATUS (child_status));
else
printf ("the child process exited abnormally\n");

printf ("done with main program\n");
*/
}


Compiling:

gcc -o libgonativelib.so -shared -Wl,-soname,libgonative.so -I/opt/jdk1.6.0_18/include -I/opt/jdk1.6.0_18/include/linux gonativelib.c -static -lc


Execution:

java -Djava.library.path=. TestBash