JNI interface returns byte[] instead of String

This allows the output fields of any command to contain nul characters, which
paves the way for extracting a manifest or other binary data directly instead
of having to write it into a temporary file.
This commit is contained in:
Andrew Bettison 2012-11-07 17:12:04 +10:30
parent 4450116472
commit 5185c944c9
4 changed files with 68 additions and 39 deletions

View File

@ -83,20 +83,25 @@ static int outv_growbuf(size_t needed)
static int outv_end_field()
{
outv_growbuf(1);
*outv_current++ = '\0';
jstring str = (jstring)(*jni_env)->NewStringUTF(jni_env, outv_buffer);
size_t length = outv_current - outv_buffer;
outv_current = outv_buffer;
if (str == NULL) {
jbyteArray arr = (*jni_env)->NewByteArray(jni_env, length);
if (arr == NULL) {
jni_exception = 1;
return WHY("Exception thrown from NewStringUTF()");
return WHY("Exception thrown from NewByteArray()");
}
(*jni_env)->CallBooleanMethod(jni_env, outv_list, listAddMethodId, str);
(*jni_env)->SetByteArrayRegion(jni_env, arr, 0, length, (jbyte*)outv_buffer);
DEBUGF("SetByteArrayRegion(%s)", alloca_toprint(-1, outv_buffer, length));
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from SetByteArrayRegion()");
}
(*jni_env)->CallBooleanMethod(jni_env, outv_list, listAddMethodId, arr);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from CallBooleanMethod()");
}
(*jni_env)->DeleteLocalRef(jni_env, str);
(*jni_env)->DeleteLocalRef(jni_env, arr);
return 0;
}
@ -215,6 +220,32 @@ int cli_putchar(char c)
return putchar(c);
}
/* Write a buffer of data to output. If in a JNI call, then this appends the data to the
current output field, including any embedded nul characters. Returns a non-negative integer on
success, EOF on error.
*/
int cli_write(const unsigned char *buf, size_t len)
{
#ifdef HAVE_JNI_H
if (jni_env) {
size_t avail = outv_limit - outv_current;
if (avail < len) {
memcpy(outv_current, buf, avail);
outv_current = outv_limit;
if (outv_growbuf(len) == -1)
return EOF;
len -= avail;
buf += avail;
}
memcpy(outv_current, buf, len);
outv_current += len;
return 0;
}
else
#endif
return fwrite(buf, len, 1, stdout);
}
/* Write a null-terminated string to output. If in a JNI call, then this appends the string to the
current output field. The terminating null is not included. Returns a non-negative integer on
success, EOF on error.
@ -222,21 +253,8 @@ int cli_putchar(char c)
int cli_puts(const char *str)
{
#ifdef HAVE_JNI_H
if (jni_env) {
size_t len = strlen(str);
size_t avail = outv_limit - outv_current;
if (avail < len) {
strncpy(outv_current, str, avail);
outv_current = outv_limit;
if (outv_growbuf(len) == -1)
return EOF;
len -= avail;
str += avail;
}
strncpy(outv_current, str, len);
outv_current += len;
return 0;
}
if (jni_env)
return cli_write((const unsigned char *) str, strlen(str));
else
#endif
return fputs(str, stdout);
@ -307,11 +325,21 @@ void cli_flush()
int app_echo(int argc, const char *const *argv, struct command_line_option *o, void *context)
{
if (debug & DEBUG_VERBOSE) DEBUG_argv("command", argc, argv);
int i;
for (i = 1; i < argc; ++i) {
int i = 1;
int escapes = 0;
if (i < argc && strcmp(argv[i], "-e") == 0) {
escapes = 1;
++i;
}
for (; i < argc; ++i) {
if (debug & DEBUG_VERBOSE)
DEBUGF("echo:argv[%d]=%s", i, argv[i]);
cli_puts(argv[i]);
DEBUGF("echo:argv[%d]=\"%s\"", i, argv[i]);
if (escapes) {
unsigned char buf[strlen(argv[i])];
size_t len = str_fromprint(buf, argv[i]);
cli_write(buf, len);
} else
cli_puts(argv[i]);
cli_delim(NULL);
}
return 0;

View File

@ -26,18 +26,18 @@ import java.util.LinkedList;
class ServalD
{
int status;
List<String> outv;
List<byte[]> outv;
public ServalD()
{
System.loadLibrary("servald");
}
public native int rawCommand(List<String> outv, String... args);
public native int rawCommand(List<byte[]> outv, String... args);
public void command(String... args)
{
this.outv = new LinkedList<String>();
this.outv = new LinkedList<byte[]>();
this.status = this.rawCommand(this.outv, args);
}
@ -45,8 +45,8 @@ class ServalD
{
ServalD servald = new ServalD();
servald.command(args);
for (String s: servald.outv) {
System.out.println(s);
for (byte[] a: servald.outv) {
System.out.println(new String(a));
}
System.exit(servald.status);
}

View File

@ -11,7 +11,7 @@ class ServalDTests
public static void main(String[] args)
{
try {
Class cls = new Object() { }.getClass().getEnclosingClass();
Class<?> cls = new Object() { }.getClass().getEnclosingClass();
Method m = cls.getMethod(args[0], String[].class);
m.invoke(null, (Object) Arrays.copyOfRange(args, 1, args.length));
}
@ -29,9 +29,9 @@ class ServalDTests
for (int i = 0; i != repeat; ++i) {
servald.command(Arrays.copyOfRange(args, 1, args.length));
System.out.print(servald.status);
for (String s: servald.outv) {
for (byte[] a: servald.outv) {
System.out.print(":");
System.out.print(s);
System.out.print(new String(a));
}
System.out.println("");
}

View File

@ -24,6 +24,7 @@ source "${0%/*}/../testconfig.sh"
setup() {
setup_servald
executeOk_servald config set debug.verbose 1
assert_echo_works
compile_java_classes
setup_servald_so
@ -32,21 +33,21 @@ setup() {
compile_java_classes() {
assert --message='Java compiler was detected by ./configure' [ "$JAVAC" ]
mkdir classes
assert $JAVAC -d classes "$servald_source_root"/java/org/servalproject/servald/*.java
assert $JAVAC -Xlint:unchecked -d classes "$servald_source_root"/java/org/servalproject/servald/*.java
assert [ -r classes/org/servalproject/servald/ServalD.class ]
assert [ -r classes/org/servalproject/servald/ServalDTests.class ]
}
# Make sure that the normal echo command-line works, without JNI.
assert_echo_works() {
executeOk $servald echo 'Hello,' 'world!'
assertStdoutIs -e 'Hello,\nworld!\n'
executeOk $servald echo -e 'Hello,\ttab' 'world\0!'
assertStdoutIs -e 'Hello,\ttab\nworld\0!\n'
}
doc_Echo="Serval JNI echo Hello world"
test_Echo() {
executeOk java -classpath "$PWD/classes" org.servalproject.servald.ServalD echo 'Hello,' 'world!'
assertStdoutIs -e 'Hello,\nworld!\n'
executeOk java -classpath "$PWD/classes" org.servalproject.servald.ServalD echo -e 'Hello,\ttab' 'world\0!'
assertStdoutIs -e 'Hello,\ttab\nworld\0!\n'
}
doc_Delim="Serval non-JNI output delimiter environment variable"