From 5185c944c97bb627f494d6599d947eb75b530714 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Wed, 7 Nov 2012 17:12:04 +1030 Subject: [PATCH] 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. --- commandline.c | 80 +++++++++++++------ java/org/servalproject/servald/ServalD.java | 10 +-- .../servalproject/servald/ServalDTests.java | 6 +- tests/jni | 11 +-- 4 files changed, 68 insertions(+), 39 deletions(-) diff --git a/commandline.c b/commandline.c index e21307e3..b1045c8a 100644 --- a/commandline.c +++ b/commandline.c @@ -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; diff --git a/java/org/servalproject/servald/ServalD.java b/java/org/servalproject/servald/ServalD.java index fdb21139..70f5484c 100644 --- a/java/org/servalproject/servald/ServalD.java +++ b/java/org/servalproject/servald/ServalD.java @@ -26,18 +26,18 @@ import java.util.LinkedList; class ServalD { int status; - List outv; + List outv; public ServalD() { System.loadLibrary("servald"); } - public native int rawCommand(List outv, String... args); + public native int rawCommand(List outv, String... args); public void command(String... args) { - this.outv = new LinkedList(); + this.outv = new LinkedList(); 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); } diff --git a/java/org/servalproject/servald/ServalDTests.java b/java/org/servalproject/servald/ServalDTests.java index 4546196f..9574cc32 100644 --- a/java/org/servalproject/servald/ServalDTests.java +++ b/java/org/servalproject/servald/ServalDTests.java @@ -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(""); } diff --git a/tests/jni b/tests/jni index 3ab8167e..f23309db 100755 --- a/tests/jni +++ b/tests/jni @@ -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"