我正在尝试使用JNA创建与FUSE库的绑定,但我在路上遇到了障碍。我尽可能地减少了代码,使其在这里易于理解。
FUSE库附带了一些用C编写的示例文件系统。最简单的是hello.c
。以下是其代码的最小化版本,只需在文件系统函数中进行一些打印:
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);
}
这可以使用gcc -Wall hello.c -o hello -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse -pthread -lfuse -lrt -ldl
并使用./hello.c -f /some/mount/point
-f
标记是为了让它保持在前台,以便您可以看到printf()
正在工作。
所有这些都运作良好,您可以看到printf()
正确执行。我试图使用JNA在Java中复制相同的东西。以下是我提出的建议:
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
可以使用以下代码编译:{{1}}
使用javac -cp path/to/jna.jar FuseTemp.java
出现的错误是:java -cp path/to/jna.jar:. FuseTemp
。
我在同一个mountpoint文件夹中以相同的权限执行这两个程序,并且我在fusermount: failed to access mountpoint /some/mount/point: Permission denied
组中。我正在使用:
所以我的问题是:这两个程序(fuse
和hello.c
)之间究竟有什么不同,以及如何让它们做同样的事情?
提前致谢。
修改:以下是其他一些信息。
挂载点的初始FuseTemp.java
:
stat
输出我以常规用户身份运行Java程序:
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)
在此之后,尝试执行Mounting
fusermount: failed to access mountpoint /some/mount/point: Permission denied
Result: 1
Mounted
(program exits with return code 0)
会出现以下错误消息:
stat
/ some / mount / point':传输端点未连接`
那是因为Java程序不再运行,所以fuse不能调用它的回调。要卸载,如果我尝试stat: cannot stat
,我会得到:
fusermount -u /some/mount/point
如果我尝试fusermount: entry for /some/mountpoint not found in /etc/mtab
,则会成功卸载mountpoint,并且sudo fusermount -u /some/mount/point
没有输出。 fusermount
是chmod'd 644(/etc/mtab
),因此我的用户可以阅读它,但它不包含-rw-r--r--
。成功卸载后,mountpoint将恢复其旧权限(777目录)。
现在,以root身份运行java程序:
/some/mount/point
之后,Mounting
Result: 1
Mounted
(program exits with return code 0)
stat
显示尚未修改的内容,即它仍然是777目录。
我还重写了/some/mount/point
以将所有FuseTemp.java
包括为Callback
而不是Callback
s。但是,行为是一样的。
我查看了fuse的源代码,错误代码1可以在整个执行过程中的多个点返回。我将确定它在熔断器侧的确切位置,并在此报告。
现在为Pointer
:以普通用户身份运行它,从hello.c
上的相同权限开始,并将参数/some/mount/point
和-f
传递给它,程序不会首先打印任何输出但保持运行。在挂载点上运行/some/mount/point
时,程序将打印
stat
喜欢它应该。 getattr was called
会返回错误,但这只是因为stat
的{{1}}函数没有给它任何信息,所以没有问题。以常规用户身份执行hello.c
后,程序退出并返回代码为0,卸载成功。
以root身份运行它,从getattr
上的相同权限开始并向其传递参数fusermount -u /some/mount/point
和/some/mount/point
,程序最初不会打印任何输出但会继续运行。在挂载点上运行-f
时,我收到权限错误,因为我不是root用户。以root身份运行/some/mount/point
时,程序会打印
stat
喜欢它应该。以常规用户身份执行stat
getattr was called
以root身份执行fusermount -u /some/mount/point
,程序以返回码0退出,卸载成功。
答案 0 :(得分:7)
找到它。虽然回想起来这个错误确实很愚蠢,但要发现它并不容易。
解决方案:Fuse的fuse_main_real
方法的第一个参数是参数列表。在此列表中,它期望参数0是文件系统名称或一些有意义的程序名称。因此,而不是
final String[] actualArgs = { "-f", "/some/mount/point" };
应该是
final String[] actualArgs = { "programName", "-f", "/some/mount/point" };
这也意味着您不能使用Java在main
方法中提供的参数列表,因为它也不包括程序名称。
为什么重要:fuse实际上会自己解析并调用/bin/mount
传递以下参数:
--no-canonicalize -i -f -t fuse.(arg 0) -o (options) (mountpoints) ...
因此,如果您将-f /some/mount/point
作为参数列表,则fuse将尝试运行:
/bin/mount --no-canonicalize -i -f -t fuse.-f -o rw,nosuid,nodev /some/mount/point
且mount
不喜欢“fuse.-f
”并会抱怨。
如何找到:在fuse的源代码中添加一堆printf()
以找出确切失败的位置:在/lib/mount_util.c
第82行:
execl("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
"-f", "-t", type, "-o", opts, fsname, mnt, NULL);
我为假设错误是由于它与Java相关或与JNA相关或与权限相关而道歉。我将编辑问题标题和标签以反映这一点。 (在我的辩护中,错误保险丝正在返回(“许可被拒绝”)肯定没有帮助!)
感谢您的帮助ee。和technomage,我再次为你带走了一大块时间而道歉,因为结果证明这是一个愚蠢的错误。
答案 1 :(得分:2)
关于运行jar时权限被拒绝的问题...我确信这是Java安全权限,这里解释为什么在超级用户模式下运行时没有捕获到异常但是在非运行时捕获到权限被拒绝的异常 - 超级用户模式。
据我所知,Java有一层安全层,与标准C程序不同(除了一些可能包含安全检查的C库,就像.NET托管C ++库一样)。即使文件操作函数来自libfuse.so
,它也可能调用可能在系统内核内存空间中执行的Linux系统内核调用。因为它现在通过Java运行,Java必须将所有库函数(包括系统调用)加载/映射到内存中。如果Java发现内存映射发生在系统内核内存空间而不是执行期间的用户内存空间,它将引用其安全管理器来检查Java程序的当前用户状态。
否则,权限被拒绝错误实际上可能来自保险丝试图访问受普通用户限制的挂载点,这是预期的行为。然后,这与Java无关。但是,这个错误也应该在C程序中发生。但是,从你的帖子和评论来看,它并没有说明多少。
但是,以root身份运行程序不会导致出现错误。 唉,它似乎没有做任何事情:它只是说“Mounting”和 “立即安装”。所以它确实完成了,但是 fuse_main_real调用立即返回。它返回的数字是1。 这是一些进步,但该计划需要作为一个可运行 像hello.c这样的常规用户可以。
另一方面,根据您最近的评论,似乎StructFuseOperations
结构中的函数指针(回调)字段无法“触发”保险丝可能调用的任何保险丝事件。
注意:我认为“错误的”主Java程序显示“挂载”和“挂载”,它们之间没有任何其他内容实际涉及调用fuse_main_real
方法在超级用户模式下运行程序时,不会启动任何保险丝事件,但返回代码为1。我没有尝试过帖子中的代码,因为我现在无法访问Linux操作系统。
更新:从此时起,在OP最近发布的帖子更新后,关于JNA结构中的回调填充的讨论不再有效:https://stackoverflow.com/revisions/e28dc30b-9b71-4d65-8f8a-cfc7a3d5231e/view-source
根据给定的链接fuse_operations Struct Reference,您只关注C结构的几个字段,如下所示:
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 **/
};
但是,您似乎尝试使用填充跳过一些回调字段。因此,为了维护fuse_operations
结构中回调字段的排列顺序,可以将Pointer
类型应用于您跳过的每个回调字段。但是,通过为这些跳过的结构字段假设一个简单的Pointer
字段,您已删除了有关每个字段的回调的重要信息:其回调签名。
来自JNA API概述:
回调(功能指针)
JNA支持向本机代码提供Java回调。你必须定义 一个扩展Callback接口的接口,并定义一个 带有与函数指针匹配的签名的回调方法 本机代码所需。方法的名称可能是某种东西 只有在只有一个方法时才会出现“回调” 扩展Callback或实现的类的接口 打回来。参数和返回值遵循相同的规则 直接函数调用。
如果回调返回String或String [],则返回的内存将返回 在返回的对象是GC后才有效。
以下是概述中的建议:
// 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);
但是,它没有建议在结构中使用填充技术正确跳过回调的方法,特别是当它太大并且您不想定义您不感兴趣的每个回调时。也许,这是没有理由的,可能会导致像你所面对的那样不明确的行为......
对于您要填充的每个回调字段,可能不是Pointer
,而是可以使用Callback
字段,维护其字段名称,如规范中所示。您可能会或可能不会使用空值初始化它(我没有尝试过这个;可能它不起作用)。
<强>更新强>
上面我的建议似乎可以基于C callback with JNA makes JRE crash中tgdavies的无关JNA解决方案,在那里他填充了他对简单Callback
类型不感兴趣的回调字段,但匹配的回调字段名称在sp_session_callbacks
结构中保持不变。
我想,由于fuse_operations
结构不正确,fuse_main_real
无法启动您感兴趣的预期保险丝事件。