I am attempting to call JNIEnv::NewObject() in some JNI code when a C function returns a non-zero error code.
The order of events looks like:
Call C function.
If return code is non-zero, call a helper function which throws a custom excpetion.
The class I am trying to construct so that I can throw it is:
public final class HseException extends Exception {
private static final long serialVersionUID = 8995408998818557762L;
private final int errno;
private final Context ctx;
/* Only called from C */
HseException(final String message, final int errno, final Context ctx) {
super(message);
this.errno = errno;
this.ctx = ctx;
}
public Context getContext() {
return this.ctx;
}
public int getErrno() {
return this.errno;
}
public static enum Context {
NONE
}
}
In my code I am caching the jclass and jmethodID for the class and the constructor in a global struct, but the code looks like:
globals.com.micron.hse.HseException.class =
(*env)->FindClass(env, "com/micron/hse/HseException");
globals.com.micron.hse.HseException.init = (*env)->GetMethodID(
env,
globals.com.micron.hse.HseException.class,
"<init>",
"(Ljava/lang/String;ILcom/micron/hse/HseException$Context;)V");
globals.com.micron.hse.HseException.Context.class =
(*env)->FindClass(env, "com/micron/hse/HseException$Context");
globals.com.micron.hse.HseException.Context.NONE = (*env)->GetStaticFieldID(
env,
globals.com.micron.hse.HseException.Context.class,
"NONE",
"Lcom/micron/hse/HseException$Context;");
Note that the above code is located in the JNI_OnLoad() function of my library. This function completes without error, so this tells me that at least my classes and methods are being loaded correctly.
Lastly here is my helper function where I throw my custom exception type:
/* hse_err_t is a scalar type.
* hse_strerror() creates a string out of that scalar.
* hse_err_to_ctx() gets the enum context value embedded within the scalar.
* hse_err_to_errno() gets the errno value embedded within the scalar.
*/
jint
throw_new_hse_exception(JNIEnv *env, hse_err_t err)
{
assert(env);
assert(err);
const size_t needed_sz = hse_strerror(err, NULL, 0);
char *buf = malloc(needed_sz + 1);
if (!buf)
return (*env)->ThrowNew(
env,
globals.java.lang.OutOfMemoryError.class,
"Failed to allocate memory for error buffer");
hse_strerror(err, buf, needed_sz + 1);
const jstring message = (*env)->NewStringUTF(env, buf);
free(buf);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const int rc = hse_err_to_errno(err);
const enum hse_err_ctx ctx = hse_err_to_ctx(err);
jfieldID err_ctx_field = NULL;
switch (ctx) {
case HSE_ERR_CTX_NONE:
err_ctx_field = globals.com.micron.hse.HseException.Context.NONE;
break;
}
assert(err_ctx_field);
const jobject err_ctx_obj = (*env)->GetStaticObjectField(
env, globals.com.micron.hse.HseException.Context.class, err_ctx_field);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const jobject hse_exception_obj = (*env)->NewObject(
env,
globals.com.micron.hse.HseException.class,
globals.com.micron.hse.HseException.init,
message,
rc,
err_ctx_obj);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
return (*env)->Throw(env, (jthrowable)hse_exception_obj);
}
I know for a fact that the (*env)->NewObject() call is what is raising the exception because an exception check before and after will tell me so. The (*env)->NewStringUTF() call is successful and contains the string it should contain. The context field is also retrieved successfully.
What I am not understanding is why I am getting an InstantiationException. The Throws section of the JNIEnv::NewObject() is marked as the following:
THROWS:
InstantiationException: if the class is an interface or an abstract class.
OutOfMemoryError: if the system runs out of memory.
Any exceptions thrown by the constructor.
My class is not an interface nor is it an abstract class, so where could this exception be generated from? The weird thing is that I swear this worked before, but since I am writing these Java bindings from scratch, I have just been overwriting commits and force pushing to my branch.
Any help is appreciated. Unfortunately getMessage() on the exception returns null which just isn't helpful at all. There is no message from the JVM telling me potentially what I have done wrong either.
One detail that could be helpful is that when I try to call JNIEnv::ThrowNew() (after putting a (Ljava/lang/String;)V constructor in the same HseException class, jni_ThrowNew() segfaults, and I cannot understand why. The class is valid when I stash the jclass, and I know for a fact that the memory it is stashed in isn't overwritten in any way, since I have checked the pointer.
The repo where all this code lives is: https://github.com/hse-project/hse-java. Unfinished product, but at least it is buildable and tests can be ran. In the event that someone decides to clone the repo and build it, I will repeat the directions here:
meson build
ninja -C build
meson test -C build -t 0 KvsTest # I am using this test to exercise the code path
My goal tomorrow will be to try to reproduce the issue in a smaller manner. I may also try to peer into the OpenJDK code assuming that is where the JNI interfaces live. Figure if I look hard enough, I might find the line of code which generates the exception.
Edit: I did a test where in my current code, I added a main function and a native function whose only purpose is to throw an exception from C. The code looks something like:
private static native void throwException();
public static void main(String[] args) {
System.load("/path/to/.so");
throwException();
}
The implementation of the native function is:
void Java_com_micron_hse_Hse_throwException
(JNIEnv *env, jclass hse_cls)
{
(void)hse_cls;
/* Generate error */
hse_err_t err = hse_kvdb_txn_begin(NULL, NULL);
throw_new_hse_exception(env, err);
}
This printed the following after executing java -jar path/to/jar:
Exception in thread "main" com.micron.hse.HseException: lib/binding/kvdb_interface.c:1046: Invalid argument (22)
at com.micron.hse.Hse.throwException(Native Method)
at com.micron.hse.Hse.main(Hse.java:28)
That is exactly what I expect to be printed, so now I would say I am even more lost than when I started. For some reason in the context of my tests, the InstantiationException is raised. Not sure if an application using the JAR would hit the same issue or if it is just a test context thing.
Edit 2:
Changed the main method from the previous edit to the following which is pretty much exactly what my test does:
public static void main(String[] args) throws HseException {
try {
loadLibrary(Paths.get("/home/tpartin/Projects/hse-java/build/src/main/c/libhsejni-2.so"));
init();
final Kvdb kvdb = Kvdb.open(Paths.get("/media/hse-tests"));
final Kvs kvs = kvdb.kvsOpen("kvs");
kvs.delete((byte[])null);
kvs.close();
kvdb.close();
} finally {
// fini();
}
}
And was able throw the exception from C appropriately. This must mean that something is wrong with my test environment somehow.
Edit 3: Another clue. On one test, this issue generates the InstantiationException. On another test, this issue segfaults in jni_NewObject.
My issue was that I was holding onto jclass et al. references for too long.
Prior question: Why I should not reuse a jclass and/or jmethodID in JNI?
Java docs: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references
All Java objects returned by JNI functions are local references.
Thanks to Andrew Henle for pointing this out in the comments of the question. I have highlighted his comment in this answer, and will mark it is as the answer.
Related
I have a C (navive) program and a jar file with the main() method. From my native program I am initializing the JVM, and calling the main() method. I have no problems with this, everything is completely fine. But then I wanted to call back a C function from my java code.
The C function is defined in the native code in the same module as the one, that have created the JVM. The header is auto-generated, and the body is as simple as this:
JNIEXPORT void JNICALL Java_eu_raman_chakhouski_NativeUpdaterBus_connect0(JNIEnv* env, jclass clazz)
{
return;
}
So, from the java code I'm calling NativeUpdaterBus.connect0(), continuosly getting an UnsatisfiedLinkError. I have no System.loadLibrary() calls in my java code, because I thought, that there will be no problems calling the native code back from the java code if the target module is (possibly?) already loaded.
Well, maybe my approach is completely incorrect, but I can't see any obvious defects, maybe you could help?
What possibly could help (but I didn't tried any of these approaches, because I'm still not quite sure)
Use a kind of a "trampoline" dynamic library with these JNI methods, load it from the java code, then marshal native calls through it.
Define a java.lang.Runnable's anonymous inheritor, created with jni_env->DefineClass() but this involves some bytecode trickery.
Use an another, less invasive approach, like sockets, named pipes, etc. But in my case I'm using only one native process, so this might be an overkill.
I'm using OpenJDK 11.0.3 and Windows 10. My C program is compiled with the Microsoft cl.exe 19.16.27031.1 for x64 (Visual Studio 2017).
One possibility, as others have already mentioned, is to create a shared library (.dll) and call it from the native code and from Java to exchange data.
However, if you want to callback to a C function defined in the native code in the same module as the one the JVM originally created, you can use RegisterNatives.
Simple Example
C program creates JVM
it calls a Main of a class
the Java Main calls back a C function named connect0 in the calling C code
to have a test case the native C function constructs a Java string and returns it
the Java side prints the result
Java
package com.software7.test;
public class Main {
private native String connect0() ;
public static void main(String[] args) {
Main m = new Main();
m.makeTest(args);
}
private void makeTest(String[] args) {
System.out.println("Java: main called");
for (String arg : args) {
System.out.println(" -> Java: argument: '" + arg + "'");
}
String res = connect0(); //callback into native code
System.out.println("Java: result of connect0() is '" + res + "'"); //process returned String
}
}
C Program
One can create the Java VM in C as shown here
(works not only with cygwin but still with VS 2019) and then register with RegisterNatives native C callbacks. So using the function invoke_class from the link above it could look like this:
#include <stdio.h>
#include <windows.h>
#include <jni.h>
#include <stdlib.h>
#include <stdbool.h>
...
void invoke_class(JNIEnv* env) {
jclass helloWorldClass;
jmethodID mainMethod;
jobjectArray applicationArgs;
jstring applicationArg0;
helloWorldClass = (*env)->FindClass(env, "com/software7/test/Main");
mainMethod = (*env)->GetStaticMethodID(env, helloWorldClass, "main", "([Ljava/lang/String;)V");
applicationArgs = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), NULL);
applicationArg0 = (*env)->NewStringUTF(env, "one argument");
(*env)->SetObjectArrayElement(env, applicationArgs, 0, applicationArg0);
(*env)->CallStaticVoidMethod(env, helloWorldClass, mainMethod, applicationArgs);
}
jstring connect0(JNIEnv* env, jobject thiz);
static JNINativeMethod native_methods[] = {
{ "connect0", "()Ljava/lang/String;", (void*)connect0 },
};
jstring connect0(JNIEnv* env, jobject thiz) {
printf("C: connect0 called\n");
return (*env)->NewStringUTF(env, "Some Result!!");
}
static bool register_native_methods(JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, "com/software7/test/Main");
if (clazz == NULL) {
return false;
}
int num_methods = sizeof(native_methods) / sizeof(native_methods[0]);
if ((*env)->RegisterNatives(env, clazz, native_methods, num_methods) < 0) {
return false;
}
return true;
}
int main() {
printf("C: Program starts, creating VM...\n");
JNIEnv* env = create_vm();
if (env == NULL) {
printf("C: creating JVM failed\n");
return 1;
}
if (!register_native_methods(env)) {
printf("C: registering native methods failed\n");
return 1;
}
invoke_class(env);
destroy_vm();
getchar();
return 0;
}
Result
Links
Creating a JVM from a C Program: http://www.inonit.com/cygwin/jni/invocationApi/c.html
Registering Native Methods: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#registering-native-methods
System.loadLibrary() is essential for the jni lookup to work. You also have a more flexible System.load() alternative.
Make sure that the native method implementation is declared with extern "C" and is not hidden by linker.
I have a problem in my Java native audio library but first, here is my current approach:
With a native method I'm opening a 'global' stream, which receives data over a callback function.
The callback function runs until there is no data.
If there is no data, the stream only stops, but does not get closed.
Now I wanted to feed the stream with data again [trying to start stream again(this operation is allowed)], but the stream has already been deleted.
So now I tried to figure out how to prevent deletion of the stream from C++ or Java.
One solution was to create a thread in the stream, which prevents the deletion.
But I don't like this solution...
So I searched how to keep such objects alive and found out, that there are so called "global references" which can be made with the JNI. But I did not understand whether they are only for java objects or for both.
Also I tried out whether another pointer type of C++ could help.
I appreciate any help or ideas, it does not have to be JNI only. C++ standard library methods/functions/classes etc. are also good :) !
System information:
Compiler: MinGW64 over MSYS2
JDK8u91
Of course 64bit operation system (Does not have to be named xD)
With global stream is meant, that the stream is accessible to all JNI methods.
EDIT:
Okay, 'to let the cat out of the back' I'm using RtAudio.
Realtime C++ Audio Library
Example:
//THIS IS C++ CODE
RtAudio audio(RtAudio::WASAPI);
int callback(//Buffer stuff etc.){
//do something
if(data.isEmpty())return 1;//invokes audio.closeStream() but this does NOT closes the stream!
else return 0; //Go on with the stream rather wait for the next call
}
JNIEXPORT void JNICALL openStream(jintArray data){
//This is a outputstream
audio.openStream(&outputParams,....., &callback,....);
audio.startStream();
}
JNIEXPORT void JNICALL fillData(jintArray data){
//filldata again!
stream.start(); //Starts the stream but does nothing, because the stream is deleted because of Java
}
If I would change the openStream method to this, the stream won't be deleted but I look for a better solution...
JNIEXPORT void JNICALL openStream(jintArray data){
//This is a outputstream
audio.openStream(&outputParams,....., &callback,....);
audio.startStream();
**while(true); //ADD THIS AND THE STREAM WON'T BE DELETED!**
}
Another solution is to add into the RtAudio API a "keepInstanceAliveThread" which is called after the stopStream() method and deleted after calling startStream() or closeStream(). I would rather prefer another solution but at all, there isn't any yet.
Pre-outcomes:
Thanks to #marcinj:
global object are known to cause many problems, its hard to control their construction/destruction.
EDIT:
I found out in the internet (also on stackoverflow), that the destructor is called after the return of a JNI method.
Use a long in the Java object to hold a pointer to the C++ object.
A Java long is 64 bits, and every platform Java runs on has either 32- or 64-bit pointers. And every platform Java is supplied for will support this, despite it not being strictly-conforming C or C++ code.
Java:
// class member
private long audio
// native functions
private native long openStream( int[] data );
private native void deleteStream( long audio );
private native void nativeFillData( long audio, int[] data );
public MyClass()
{
audio = openStream( data );
}
public void fillData( int[] data )
{
nativeFillData( this.audio, data );
}
// delete the C++ object - you may want to
// control this directly and not rely on
// finalize() getting called
protected void finalize()
{
deleteStream( audio );
super.finalize();
}
C++:
JNIEXPORT jlong JNICALL openStream(jintArray data)
{
RtAudio *audio = new RtAudio(RtAudio::WASAPI);
audio->openStream(&outputParams,....., &callback,....);
audio->startStream();
// C-style cast - JNI interface is C, not C++
return( ( jlong ) audio );
}
JNIEXPORT void JNICALL deleteStream(jlong jaudio)
{
RtAudio *audio = static_cast <RtAudio *>( jaudio );
delete audio;
}
JNIEXPORT void JNICALL nativeFillData(jlong jaudio, jintArray data)
{
RtAudio *audio = static_cast <RtAudio *>( jaudio );
audio->start();
...
}
1) JAVA THREAD WAY
We can create a new thread to keep running in a JNI function locked with a monitor or conditional while loop.
Then a separate call would stop the execution of the thread in your function by releasing the monitor or changing the condition in the while loop.
2) JAVA OBJECT REFERENCE WAY
Another option is to create a Global Reference of your object
Android JNI and NewGlobalRef.
Here a separate call on USB disconnect would do DeleteGlobalRef.
We can also move the life cycle of your C++ object into java by passing it back to the java layer
keep some sort of c++ object alive over multiple jni calls.
Here a separate call on USB disconnect would remove any reference to C++ object in your java code.
Implementation
Native File (mynative.cpp)
extern "C" JNIEXPORT jobject JNICALL
Java_com_android_nativecpp_MainActivity_createJniNativeReference(JNIEnv* env, jobject obj, jint size) {
void* buf = malloc(size);
jobject sharedbytebuffer = env->NewDirectByteBuffer(buf, size);
return env->NewGlobalRef(sharedbytebuffer);
}
extern "C" JNIEXPORT void JNICALL
Java_com_android_nativecpp_MainActivity_deleteJniNativeReference(JNIEnv* env, jobject obj, jobject sharedbytebuffer) {
env->DeleteGlobalRef(sharedbytebuffer);
void* buf = env->GetDirectBufferAddress(sharedbytebuffer);
free(buf);
return;
}
Java file (MainActivity.java)
import java.nio.ByteBuffer;
private ByteBuffer mJniReference;
public native ByteBuffer createJniNativeReference(int size);
public native void deleteJniNativeReference(ByteBuffer mJniReference);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mJniReference = createJniNativeReference(1000);
}
protected void onDestroy() {
deleteJniNativeReference(mJniReference);
super.onDestroy();
}
EXPLAINATION
The reason for either of 1) or 2) is otherwise, the creating frame is then exited from the stack and the JNI local references are deleted.
This will end all the threads in C++ when all std::thread references (which are not detached, ideally) are deleted.
In the non-detached case std::thread destructors are called on the main exit, or when a thread object goes out of scope and then terminate() is called.
In the detached case the detached threads exit on app close which kills the host process. They can also be garbage collected.
In an Android app I have some JNI code that calls a java static method.
jbyteArray response = (jbyteArray)pEnv->CallObjectMethod(handlerClass, mid, jstrServiceUrl, jstrRequest);
Executing it in Android 5 in an ART environment, I get a check jni error:
JNI DETECTED ERROR IN APPLICATION: calling static method byte[] x.y.z(java.lang.String, java.lang.String) with CallObjectMethodV in call to CallObjectMethodV...
I don't get this error in Android 4 with a Dalvik environment.
The java method is this one:
public static byte[] z(String serviceURL, String request)
and is previously binded like this:
jclass handlerClass = pEnv->FindClass("x/y/z");
if (handlerClass == NULL) {
return -1;
}
mid = pEnv->GetStaticMethodID(handlerClass, "z", "(Ljava/lang/String;Ljava/lang/String;)[B");
if (mid == NULL) {
return -2;
}
// Construct Strings
jstring jstrServiceUrl = pEnv->NewStringUTF(szServiceURL);
jstring jstrRequest = pEnv->NewStringUTF(szRequest);
I don't know why your code worked with Dalvik, but the method id given to Call<type>Method must be obtained with GetMethodID. If you have a method id obtained with GetStaticMethodID you should use CallStatic<type>Method.
See the descriptions of Call<type>Method and CallStatic<type>Method in the JNI functions documentation.
I am trying to create bindings to the FUSE library using JNA, but I have hit a snag along the road. I have minimized the code as much as possible to make it digestible here.
The FUSE library comes with a few example filesystems written in C. The simplest of them is hello.c. The following is a minimized version of its code to simply a few prints in the filesystem functions:
hello.c:
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos#szeredi.hu>
This program can be distributed under the terms of the GNU GPL.
See the file COPYING.
gcc -Wall hello.c -o hello `pkg-config fuse --cflags --libs`
*/
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
static int hello_getattr(const char *path, struct stat *stbuf)
{
printf("getattr was called\n");
return 0;
}
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
printf("readdir was called\n");
return 0;
}
static int hello_open(const char *path, struct fuse_file_info *fi)
{
printf("open was called\n");
return 0;
}
static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
printf("read was called\n");
return 0;
}
static struct fuse_operations hello_oper = {
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};
int main(int argc, char *argv[])
{
return fuse_main_real(argc, argv, &hello_oper, sizeof(hello_oper), NULL);
}
This can be compiled using gcc -Wall hello.c -o hello -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse -pthread -lfuse -lrt -ldl
And invoked with ./hello.c -f /some/mount/point
The -f flag is to make it stay in the foreground so that you can see the printf()'s working.
All of this works well, you can see the printf()'s executing properly. I am trying to replicate the same thing in Java using JNA. Here is what I came up with:
FuseTemp.java:
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
public class FuseTemp
{
public static interface Fuse extends Library
{
int fuse_main_real(int argc, String[] argv, StructFuseOperations op, long size, Pointer user_data);
}
#SuppressWarnings("unused")
public static class StructFuseOperations extends Structure
{
public static class ByReference extends StructFuseOperations implements Structure.ByReference
{
}
public Callback getattr = new Callback()
{
public int callback(final String path, final Pointer stat)
{
System.out.println("getattr was called");
return 0;
}
};
public Callback readlink = null;
public Callback mknod = null;
public Callback mkdir = null;
public Callback unlink = null;
public Callback rmdir = null;
public Callback symlink = null;
public Callback rename = null;
public Callback link = null;
public Callback chmod = null;
public Callback chown = null;
public Callback truncate = null;
public Callback utime = null;
public Callback open = new Callback()
{
public int callback(final String path, final Pointer info)
{
System.out.println("open was called");
return 0;
}
};
public Callback read = new Callback()
{
public int callback(final String path, final Pointer buffer, final long size, final long offset, final Pointer fi)
{
System.out.println("read was called");
return 0;
}
};
public Callback write = null;
public Callback statfs = null;
public Callback flush = null;
public Callback release = null;
public Callback fsync = null;
public Callback setxattr = null;
public Callback getxattr = null;
public Callback listxattr = null;
public Callback removexattr = null;
public Callback opendir = null;
public Callback readdir = new Callback()
{
public int callback(final String path, final Pointer buffer, final Pointer filler, final long offset,
final Pointer fi)
{
System.out.println("readdir was called");
return 0;
}
};
public Callback releasedir = null;
public Callback fsyncdir = null;
public Callback init = null;
public Callback destroy = null;
public Callback access = null;
public Callback create = null;
public Callback ftruncate = null;
public Callback fgetattr = null;
public Callback lock = null;
public Callback utimens = null;
public Callback bmap = null;
public int flag_nullpath_ok;
public int flag_reserved;
public Callback ioctl = null;
public Callback poll = null;
}
public static void main(final String[] args)
{
final String[] actualArgs = { "-f", "/some/mount/point" };
final Fuse fuse = (Fuse) Native.loadLibrary("fuse", Fuse.class);
final StructFuseOperations.ByReference operations = new StructFuseOperations.ByReference();
System.out.println("Mounting");
final int result = fuse.fuse_main_real(actualArgs.length, actualArgs, operations, operations.size(), null);
System.out.println("Result: " + result);
System.out.println("Mounted");
}
}
The definition of the the fuse_operations struct can be found here.
This can be compiled using: javac -cp path/to/jna.jar FuseTemp.java
And invoked using java -cp path/to/jna.jar:. FuseTemp
jna.jar is available here.
The error that comes up is: fusermount: failed to access mountpoint /some/mount/point: Permission denied.
I am executing both programs as the same user with the same permissions on the same mountpoint folder, and I am in the fuse group. I am using:
Linux kernel 3.0.0
FUSE 2.8.4
OpenJDK 1.6.0_23
JNA 3.4.0
So my question is: What exactly is different between these two programs (hello.c and FuseTemp.java), and how to make them do the same thing?
Thanks in advance.
Edit: Here is some additional info.
Initial stat of the mountpoint:
File: `/some/mount/point'
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 803h/2051d Inode: 540652 Links: 2
Access: (0777/drwxrwxrwx) Uid: ( 1000/ myusername) Gid: ( 1000/ myusername)
Output I get from running the Java program as regular user:
Mounting
fusermount: failed to access mountpoint /some/mount/point: Permission denied
Result: 1
Mounted
(program exits with return code 0)
After this, trying to execute stat gives the following error message:
stat: cannot stat/some/mount/point': Transport endpoint is not connected`
That is because the Java program isn't running anymore, so fuse cannot call its callbacks. To unmount, if I try fusermount -u /some/mount/point, I get:
fusermount: entry for /some/mountpoint not found in /etc/mtab
And if I try sudo fusermount -u /some/mount/point, the mountpoint is successfully unmounted and there is no output from fusermount. /etc/mtab is chmod'd 644 (-rw-r--r--) so my user can read it, but it doesn't contain /some/mount/point. After a successful unmount, the mountpoint is back to its old permissions (777 directory).
Now, running the java program as root:
Mounting
Result: 1
Mounted
(program exits with return code 0)
After that, stating /some/mount/point shows that is has not been modified, i.e. it is still a 777 directory.
I have also rewritten FuseTemp.java to include all Callbacks as Callbacks instead of Pointers. The behavior is the same, however.
I looked at fuse's source code and the error code 1 can be returned at multiple points throughout the execution. I will pinpoint where exactly is it failing on the fuse side and report back here.
Now for hello.c: running it as regular user, starting with the same permissions on /some/mount/point and passing it the arguments -f and /some/mount/point, the program doesn't print any output at first but keeps running. When running stat on the mountpoint, the program prints
getattr was called
like it should. stat returns an error, but that's simply because hello.c's getattr function doesn't give it any information, so no problems there. After executing fusermount -u /some/mount/point as regular user, the program exits with return code 0 and the unmount is successful.
Running it as root, starting with the same permissions on /some/mount/point and passing it the arguments -f and /some/mount/point, the program doesn't print any output at first but keeps running. When running stat on the mountpoint, I get a permission error because I am not root. When running stat on it as root, the program prints
getattr was called
like it should. Executing fusermount -u /some/mount/point as regular user yields
fusermount: entry for /some/mount/point not found in /etc/mtab
Executing fusermount as root, the program exits with return code 0 and the unmount is successful.
Found it. While the error was really silly in retrospect, it wasn't easy to spot.
The solution: Fuse's fuse_main_real method's first argument is an argument list. In this list, it expects argument 0 to be the filesystem name, or some meaningful program name. Thus, instead of
final String[] actualArgs = { "-f", "/some/mount/point" };
It should have been
final String[] actualArgs = { "programName", "-f", "/some/mount/point" };
This also means that you can't use the argument list that Java gives you in your main method, since that doesn't include the program name either.
Why it matters: fuse actually does its own argument parsing and calls /bin/mount passing it the following arguments:
--no-canonicalize -i -f -t fuse.(arg 0) -o (options) (mountpoints) ...
As such, if you give if -f /some/mount/point as argument list, fuse will try to run:
/bin/mount --no-canonicalize -i -f -t fuse.-f -o rw,nosuid,nodev /some/mount/point
And mount doesn't like "fuse.-f" and will complain.
How it was found: Adding a bunch of printf() inside fuse's source code to figure out where exactly things were failing: in /lib/mount_util.c at line 82:
execl("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
"-f", "-t", type, "-o", opts, fsname, mnt, NULL);
I apologise for assuming the error was due to it being Java-related or JNA-related or permissions-related. I will edit the question title and tags to reflect this. (In my defense, the error fuse was returning ("Permission denied") certainly wasn't helpful!)
Thank you for your assistance ee. and technomage, and again I apologise for taking away a chunk of your time because of what turned out to be a silly mistake.
Regarding the permission denied issue when running the jar...I am sure it is Java security permission thing is going on here to explain why no exception is caught when running in superuser mode but permission denied exception is caught when running in non-superuser mode.
From what I can understand, Java has a layer of security unlike the standard C program (except for some C libraries that may include security checks, just like .NET managed C++ libraries). Even though the file manipulation functions are coming from libfuse.so, it may also call Linux system kernel calls that may be executed within system kernel memory space. Since it is now running via Java where Java has to load/map all library functions including system calls into memory. If Java finds out the memory map occurs in system kernel memory space rather than user memory space during execution, it will refer its security manager to check against the current user state of the Java program.
Otherwise, the permission denied error may actually come from fuse trying to access a mount point that is restricted from the normal user which is an expected behavior. Then, this has nothing to do with Java. But, this error shall also occur in C program as well. But, from your post and comments, it doesn't tell that much.
However, running the program as root didn't cause the error to appear.
Alas, it didn't seem to do anything: It just said "Mounting" and
"Mounted" instantly. So it does go up to completion, but the
fuse_main_real call returns instantly. The number it returns is 1.
That is some progress, but the program needs to be runnable as a
regular user like hello.c can.
On the other hand, based on your recent comment above, it seems that your function pointer (callback) fields in StructFuseOperations structure are not working to "fire up" any fuse event that fuse may invoke.
Note: I assume that the "erroneous" main Java program displays "Mounting" and "Mounted" and nothing else in between them which actually involves a call to fuse_main_real method that doesn't fire up any fuse event but a return code of 1 when running the program in the superuser mode. I haven't tried the code in the post since I don't have access to Linux OS right now.
Update: from this point onwards, the discussion about callback padding in a JNA structure is no longer valid after the recent post update made by OP: https://stackoverflow.com/revisions/e28dc30b-9b71-4d65-8f8a-cfc7a3d5231e/view-source
Based on the given link, fuse_operations Struct Reference, you only focus on a few fields of the C structure as follows:
static struct fuse_operations hello_oper = {
int (getattr*)(const char *path, struct stat *stbuf);
/** some 12 skipped callbacks in between **/
int (open*)(const char *path, struct fuse_file_info *fi);
int (read*)(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
/** some 10 skipped callbacks in between **/
int (readdir*)(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi);
/** some 11 skipped callbacks in between **/
unsigned int flag_nullpath_ok;
unsigned int flag_reserved;
/** some 2 skipped callbacks in between **/
};
However, it seems that you are trying to skip a few callback fields with padding. Therefore, to maintain the order of how the callback fields are laid out in fuse_operations structure, you apply the Pointer type to each callback field that you have skipped. However, by assuming a simple Pointer field for these skipped structure fields, you have removed the vital information about the callback for each field: its callback signature.
From JNA API Overview:
Callbacks (Function Pointers)
JNA supports supplying Java callbacks to native code. You must define
an interface that extends the Callback interface, and define a single
callback method with a signature that matches the function pointer
required by the native code. The name of the method may be something
other than "callback" only if there is only a single method in the
interface which extends Callback or the class which implements
Callback. The arguments and return value follow the same rules as for
a direct function invocation.
If the callback returns a String or String[], the returned memory will
be valid until the returned object is GC'd.
Following is what is suggested in the overview:
// Original C code
struct _functions {
int (*open)(const char*,int);
int (*close)(int);
};
// Equivalent JNA mapping
public class Functions extends Structure {
public static interface OpenFunc extends Callback {
int invoke(String name, int options);
}
public static interface CloseFunc extends Callback {
int invoke(int fd);
}
public OpenFunc open;
public CloseFunc close;
}
...
Functions funcs = new Functions();
lib.init(funcs);
int fd = funcs.open.invoke("myfile", 0);
funcs.close.invoke(fd);
However, it doesn't suggest a way to properly skip the callbacks with padding technique in a structure especially when it is too large and you don't want to define every callback that you are not interested with. Maybe, it is not warranted and may cause undefined behavior like what you are facing...
Probably, instead of Pointer for each callback field that you want to pad, you can use Callback field, maintain its field name as in the specification. You may or may not initialize it with the null value (I haven't tried this; possibly it may not work).
Update:
It seems that my suggestion above can work based on the unrelated JNA solution by tgdavies in C callback with JNA makes JRE crash where he padded those callback fields he wasn't interested with simple Callback type but the matching callback field names remained intact in the sp_session_callbacks Structure.
I guess, because of the improper fuse_operations structure, fuse_main_real is unable to fire up the expected fuse event that you are interested with.
I have a class in C++ which takes an std::ostream as an argument in order to continuously output text (trace information). I need to get this text over to the Java side as efficiently as possible. What's the best way to do this? I was thinking of using a direct buffer, but another method would be to take all the function calls across to Java and do all the processing there, but it seems that I'd need a lot of JNI calls.
If an example could be shown of the exact implementation method, it would be very helpful, or if some code exists already to do this (perhaps part of another project). Another help would be to connect it up directly to a standard Java streaming construct, such that the entire implementation was completely transparent to the developer.
(Edit: I found Sharing output streams through a JNI interface which seems to be a duplicate, but not really of much help -- he didn't seem to find the answer he was looking for)
The std::ostream class requires a std::streambuf object for its output. This is used by the fstream and stringstream classes, which use the features of ostream by providing a custom implementation of the streambuf class.
So you can write your own std::streambuf implementation with an overwritten overflow method, buffer the incomming chars in an internal stringbuffer. Every x calls or on eof/newline generate an java-string and call the print method of your java PrintStream.
An incomplete example class:
class JavaStreamBuff : std::streambuf
{
std::stringstream buff;
int size;
jobject handle;
JNIEnv* env
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuff(JNIEnv* env, jobject jobject printStream, int buffsize = 50)
{
handle = env->NewGlobalRef(printStream);
this->env = env;
this->size = size;
}
//This method is the central output of the streambuf class, every charakter goes here
int overflow(int in)
{
if(in == eof || buff.size() == size)
{
std::string blub = buff.str();
jstring do = //magic here, convert form current locale unicode then to java string
jMethodId id = env->(env->GetObjectClass(handle),"print","(java.lang.String)V");
env->callVoidMethod(id,handle,do);
buff.str("");
}
else
{buff<<in;}
}
virtual ~JavaStreamBuff()
{
env->DeleteGlobalRef(handle);
}
}
Missing:
Multithread support (the env pointer is only valid for the jvm thread)
Error handling (checking for java exceptions thrown)
Testing(written within the last 70 min)
Native java method to set the printstream.
On the java side you need a class to convert the PrintStream to a BufferedReader.
There have to be some bugs there, haven't spend enough time to work on them.
The class requires all access to be from the thread it was created in.
Hope this helps
Note
I got it to work with visual studio but I can't get it to work with g++, will try to debug that later.
Edit
Seems that I should have looked for a more official tutorial on this bevore posting my answer, the MSDN page on this topic derives the stringbuffer in a different way.
Sorry for posting this without testing it better :-(.
A small correction to the code above in a more or less unrelated point: Just implement InputStream with a custom class and push byte[] arrays instead of Strings from c++.
The InputStream has a small interface and a BufferedReader should do most of the work.
Last update on this one, since im unable to get it to work on linux, even with the comments on the std::streambuf class stating that only overflow has to be overwritten.
This implementation pushes the raw strings into an inputstream, which can be read from by an other thread. Since I am too stupid to get the debugger working its untested, again.
//The c++ class
class JavaStreamBuf :public std::streambuf
{
std::vector<char> buff;
unsigned int size;
jobject handle;
JNIEnv* env;
public:
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuf(JNIEnv* env, jobject cppstream, unsigned int buffsize = 50)
{
handle = env->NewGlobalRef(cppstream);
this->env = env;
this->size = size;
this->setbuf(0,0);
}
//This method is the central output of the streambuf class, every charakter goes here
virtual int_type overflow(int_type in = traits_type::eof()){
if(in == std::ios::traits_type::eof() || buff.size() == size)
{
this->std::streambuf::overflow(in);
if(in != EOF)
buff.push_back(in);
jbyteArray o = env->NewByteArray(buff.size());
env->SetByteArrayRegion(o,0,buff.size(),(jbyte*)&buff[0]);
jmethodID id = env->GetMethodID(env->GetObjectClass(handle),"push","([B)V");
env->CallVoidMethod(handle,id,o);
if(in == EOF)
env->CallVoidMethod(handle,id,NULL);
buff.clear();
}
else
{
buff.push_back(in);
}
return in;
}
virtual ~JavaStreamBuf()
{
overflow();
env->DeleteGlobalRef(handle);
}
//The java class
/**
*
*/
package jx;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* #author josefx
*
*/
public class CPPStream extends InputStream {
List<Byte> data = new ArrayList<Byte>();
int off = 0;
private boolean endflag = false;
public void push(byte[] d)
{
synchronized(data)
{
if(d == null)
{
this.endflag = true;
}
else
{
for(int i = 0; i < d.length;++i)
{
data.add(d[i]);
}
}
}
}
#Override
public int read() throws IOException
{
synchronized(data)
{
while(data.isEmpty()&&!endflag)
{
try {
data.wait();
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
if(endflag)return -1;
else return data.remove(0);
}
}
Sorry for wasting so much space^^(and time :-().
It sounds as though the deliverable here is a subclass of ostream. The immediate question I'd want to be clear about is, will this class be responsible for buffering data until Java calls into it to retrieve, or is it expected to immediately (synchronously?) call via JNI to pass it on? That will be the strongest guide to how the code will shape up.
If you can reasonably expect the text to appear as a series of lines, I'd think about presenting them to Java in one line per call: this seems a fair compromise between the number of JNI calls and not unduly delaying the passing on of the text.
On the Java side I think you're looking at creating a Reader so that clients can pick up the text via a familiar interface, or perhaps a subclass of BufferedReader.