Related
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.
I have an Android app that employs both React Native and JNI. C++ (via a fork of the JUCE library) is used to generate one of the views.
React Native requires that a new instance of a view be returned from the overridden method createViewInstance(context). This seems to get called each time a React Native component containing this view is updated.
Here is my (simplified) implementation:
protected JuceViewHolder createViewInstance(ThemedReactContext themedReactContext) {
JuceBridge juceBridge = JuceBridge.getInstance();
juceViewHolder = new JuceViewHolder(themedReactContext);
// JNI method: this will trigger JuceBridge.createNewView
MainActivity.createJuceWindow(juceViewHolder);
return juceViewHolder;
}
For reference, createJuceWindow is defined as:
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, createJuceWindow, jobject, (JNIEnv* env, jclass, jobject view))
{
JuceView::getInstance().createJuceWindow(view);
}
which calls:
void createJuceWindow(jobject view)
{
viewToAttachTo = GlobalRef(view); // [GlobalRef][1] manages NewGlobalRef
myComponent = std::unique_ptr<MyComponent> (new MyComponent);
myComponent->setVisible (true);
myComponent->setOpaque(true);
if (viewToAttachTo.get() == nullptr)
DBG ("createJuceWindow: viewToAttachTo null!!!");
myComponent->setBounds(Desktop::getInstance().getDisplays().getMainDisplay().userArea);
myComponent->addToDesktop (0, viewToAttachTo.get()); // This passes in the `jobject` held by GlobalRef
}
I have separated the JuceViewHolder derived from ViewGroup, and I am passing it to a C++ function via JNI in order to attach the (Java)ComponentPeerView generated from the (C++) AndroidComponentPeer constructor
AndroidComponentPeer (Component& comp, const int windowStyleFlags, void* viewToAttachTo)
: ComponentPeer (comp, windowStyleFlags),
usingAndroidGraphics (false),
fullScreen (false),
sizeAllocated (0),
scale ((float) Desktop::getInstance().getDisplays().getMainDisplay().scale)
{
// NB: must not put this in the initialiser list, as it invokes a callback,
// which will fail if the peer is only half-constructed.
if (viewToAttachTo == nullptr) DBG ("viewToAttachTo null");
view = GlobalRef (android.bridge.callObjectMethod (JuceBridge.createNewView,
(jboolean) component.isOpaque(),
(jlong) this,
(jobject) viewToAttachTo));
if (view.get() == nullptr)
DBG ("view null!");
else
DBG ("got view.");
if (isFocused())
handleFocusGain();
}
via the createNewView method: (simplified here)
public final ComponentPeerView createNewView (boolean opaque, long host, ViewGroup viewToAttachTo)
{
ComponentPeerView v = new ComponentPeerView (viewToAttachTo.getContext(), opaque, host);
viewToAttachTo.addView(v);
return v;
}
host is a pointer to the C++ AndroidComponentPeer instance.
This works initially, however it seems that if I switch to another View or Activity and/or switch back to the C++ View, I get a crash similar to the following:
JNI ERROR (app bug): accessed deleted global reference 0x100566
art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x100566
art/runtime/java_vm_ext.cc:410] from void com.juce.JuceBridge$ComponentPeerView.focusChanged(long, boolean)
My current assumption is that the error is due to the void* pointer becoming obsolete at some point due to the garbage collector moving the underlying object, as described in this article. What I got from the article is that jobject should be used in place of a pointer.
However the method signature of the Component::addToDesktop method that I have employed uses void*:
virtual void addToDesktop (int windowStyleFlags,
void* nativeWindowToAttachTo = nullptr);
(This is the method used in iOS to pass a native UIView to attach a Component to)
Is my assumption valid? If so, is it possible to safely cast a void* pointer to a Java object, store it as a jobject (via NewGlobalRef) and then pass it back to Java?
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.
For testing purpose, I try to "fake" some objects. I want to do the following: I have an object, and want to add new methods, or overwrite some. Sadly, unlike in Java, its not possible to create "nameless" classes. Ok, I could do it by simply creating a new class, but I want to do it dinamicaly.
This is the class:
class Test
{
public function method1()
{
return 'oldmethod1';
}
public function method2()
{
return 'oldmethod2';
}
public static function staticmethod1()
{
return 'staticmethod1';
}
public static function staticmethod2()
{
return 'staticmethod2';
}
}
and now what I want to do:
$a = new Test();
$b = new CreateMockObjectFromObject($a);
$b->newmethod = function() { return 'newmethod'; };
$b->method2 = function() { return 'method2 is overwritten'; };
$b->staticmethod2 = function() { return 'staticmethod2 overwritten'; };
echo $b->method1().'<br>';
echo $b->method2().'<br>';
echo $b::staticmethod1().'<br>';
echo $b::staticmethod2().'<br>';
Here you can see my wishes: call a normal method, overwrite a method, call a static method, overwrite a method. The results: FAIL, SUCCESS, FAIL, FAIL.
I have a helper class:
class CreateMockObjectFromObject
{
private $sourceObj;
/**
* #return
*/
public function __call ($method, $args)
{
if (isset($this->sourceObj->$method))
{
return call_user_func_array($this->sourceObj->$method, $args);
}
if (isset($this->$method))
{
return call_user_func_array($this->$method, $args);
}
throw new Exception ($method.' NOT FOUND');
}
/**
* #return
*/
public static function __callStatic ($method, $args)
{
// I cant even imagine this...
}
/**
* #return CreateMockObjectFromObject
*/
public function __construct ($sourceObj)
{
$this->sourceObj = $sourceObj;
}
}
I cant even imagine what about static methods. How to write this helper class so that all mocking/faking can work? And I didnt even talk about "const"-s...
once again, I know it all could be done with extending, but I need to do in this way!
Mocking an entire class:
$mock = Mockery::mock('FQ\ClassName');
Mocking only certain methods:
$mock = Mockery::mock('FQ\ClassName[method1, method2]')
Here, method3, method4, ..., methodN will function exactly as they do in your class implementation.
Making an expectation:
$mock->shouldReceive('method1')
->once()
->andReturn('a value')
;
After you make an expectation, then you Act upon the system under test:
$return = $objectImTesting->performAction($mock);
Once you act, you should assert that any return value is what it should be, given the input you provided:
$this->assertEquals('a value', $return);
Taking the above example, the test will fail if any of the following conditions are true:
method1 on your mock object is never called
method1 on your mock object is called more than once
the return value of performAction is not the literal value 'a value'
Now, you've not reinvented the wheel and instead already gotten started writing tests. Also for free, you get everything that PHPUnit and Mockery provide you (how long do you think it would take you by yourself to be able to support Demeter Chains in your mocking framework?).
Don't get me wrong, by all means go ahead and develop your testing framework if all you're interested in is learning. However, in my opinion I would not want to trust a testing framework that I developed by myself to ensure the code that I write works. I'd much rather use something that's open source and has been around for awhile so that I can sleep easier at night.
More information:
PHPUnit Documentation
Mockery 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.