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 <[email protected]>
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:
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, stat
ing /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 Callback
s as Callback
s instead of Pointer
s. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With