11

我正在尝试使用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()s 的工作。

所有这些都运行良好,您可以看到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");
    }
}

结构的定义fuse_operations可以在这里找到

这可以使用以下方法编译:javac -cp path/to/jna.jar FuseTemp.java

并使用java -cp path/to/jna.jar:. FuseTemp

jna.jar 可在此处获得

出现的错误是:fusermount: failed to access mountpoint /some/mount/point: Permission denied

我在同一个挂载点文件夹上以具有相同权限的同一个用户身份执行这两个程序,并且我在该fuse组中。我在用:

  • Linux 内核 3.0.0
  • 保险丝 2.8.4
  • OpenJDK 1.6.0_23
  • JNA 3.4.0

所以我的问题是:这两个程序(hello.cFuseTemp.java)之间到底有什么不同,以及如何让它们做同样的事情?

提前致谢。

编辑:这是一些附加信息。

stat挂载点的初始值:

  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)

我以普通用户身份运行 Java 程序得到的输出:

Mounting
fusermount: failed to access mountpoint /some/mount/point: Permission denied
Result: 1
Mounted
(program exits with return code 0)

在此之后,尝试执行stat会给出以下错误消息:

stat: cannot stat/some/mount/point':传输端点未连接`

那是因为 Java 程序不再运行,所以 fuse 不能调用它的回调。要卸载,如果我尝试fusermount -u /some/mount/point,我会得到:

fusermount: entry for /some/mountpoint not found in /etc/mtab

如果我尝试sudo fusermount -u /some/mount/point,挂载点已成功卸载,并且没有输出fusermount/etc/mtab是 chmod'd 644 ( -rw-r--r--) 所以我的用户可以阅读它,但它不包含/some/mount/point. 成功卸载后,挂载点恢复到其旧权限(777 目录)。

现在,以 root 身份运行 java 程序:

Mounting
Result: 1
Mounted
(program exits with return code 0)

之后,stating/some/mount/point显示is没有被修改,即仍然是777目录。

我还重写FuseTemp.java了将所有Callbacks 包含为Callbacks 而不是Pointers。但是,行为是相同的。

我查看了 fuse 的源代码,错误代码 1 可以在整个执行过程中的多个点返回。我将查明保险丝端究竟在哪里发生故障并在这里报告。

现在对于hello.c: 以普通用户身份运行它,从相同的权限开始/some/mount/point并传递参数-fand /some/mount/point,程序一开始不会打印任何输出,但会继续运行。在挂载点上运行stat时,程序会打印

getattr was called

应该的。stat返回一个错误,但这仅仅是因为hello.c'getattr函数没有给它任何信息,所以那里没有问题。以普通用户身份执行后fusermount -u /some/mount/point,程序以返回码 0 退出,卸载成功。

以 root 身份运行它,以相同的权限开始/some/mount/point并传递参数-fand /some/mount/point,程序一开始不会打印任何输出,但会继续运行。在挂载点上运行stat时,我收到权限错误,因为我不是 root。当stat以 root 身份运行时,程序会打印

getattr was called

应该的。以普通用户身份执行fusermount -u /some/mount/point产生

fusermount: entry for /some/mount/point not found in /etc/mtab

以root身份执行fusermount,程序退出,返回码0,卸载成功。

4

2 回答 2

7

找到了。虽然回想起来这个错误真的很愚蠢,但它并不容易被发现。

解决方案:Fusefuse_main_real方法的第一个参数是一个参数列表。在这个列表中,它期望参数 0 是文件系统名称,或者一些有意义的程序名称。因此,而不是

final String[] actualArgs = { "-f", "/some/mount/point" };

应该是

final String[] actualArgs = { "programName", "-f", "/some/mount/point" };

这也意味着您不能在main方法中使用 Java 为您提供的参数列表,因为它也不包括程序名称。

为什么重要:fuse 实际上会进行自己的参数解析并调用/bin/mount传递以下参数:

--no-canonicalize -i -f -t fuse.(arg 0) -o (options) (mountpoints) ...

因此,如果您将 if-f /some/mount/point作为参数列表,fuse 将尝试运行:

/bin/mount --no-canonicalize -i -f -t fuse.-f -o rw,nosuid,nodev /some/mount/point

而且mount不喜欢“ fuse.-f”,会抱怨。

它是如何找到的:添加一堆printf()内部 fuse 的源代码以找出失败的确切位置:在/lib/mount_util.c第 82 行:

execl("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
      "-f", "-t", type, "-o", opts, fsname, mnt, NULL);

我很抱歉假设错误是由于它与 Java 相关或 JNA 相关或权限相关。我将编辑问题标题和标签以反映这一点。(在我的辩护中,错误保险丝正在返回(“权限被拒绝”)当然没有帮助!)

谢谢你的帮助。和技术,我再次为占用您的大部分时间而道歉,因为事实证明这是一个愚蠢的错误。

于 2012-01-18T23:49:39.933 回答
2

关于运行 jar 时的权限被拒绝问题...我确定这是 Java 安全权限的事情,这是为了解释为什么在超级用户模式下运行时没有捕获到异常,但在非超级用户模式下运行时捕获到权限被拒绝的异常.

据我了解,Java 有一层与标准 C 程序不同的安全层(除了一些可能包含安全检查的 C 库,就像 .NET 托管的 C++ 库一样)。即使文件操作函数来自libfuse.so,它也可能调用可能在系统内核内存空间内执行的 Linux 系统内核调用。由于它现在通过 Java 运行,Java 必须将所有库函数(包括系统调用)加载/映射到内存中。如果 Java 在执行过程中发现内存映射出现在系统内核内存空间而不是用户内存空间中,它将参考其安全管理器来检查 Java 程序的当前用户状态。

否则,权限被拒绝错误实际上可能来自 fuse 尝试访问受普通用户限制的挂载点,这是预期的行为。那么,这与Java无关。但是,这个错误也应该出现在 C 程序中。但是,从您的帖子和评论来看,它并没有说明太多。

但是,以 root 身份运行程序不会导致出现错误。唉,它似乎什么也没做:它只是立即说“Mounting”和“Mounted”。所以它确实会完成,但 fuse_main_real 调用会立即返回。它返回的数字是 1。这是一些进步,但程序需要像 hello.c 这样的普通用户可以运行。

另一方面,根据您上面最近的评论,您的StructFuseOperations结构中的函数指针(回调)字段似乎无法“启动” fuse 可能调用的任何熔断事件。

注意:我假设“错误”的主 Java 程序显示“Mounting”和“Mounted”,它们之间没有其他任何内容,这实际上涉及对fuse_main_real不会触发任何熔断事件但在运行时返回代码 1 的方法的调用超级用户模式下的程序。由于我现在无法访问 Linux 操作系统,因此我没有尝试过帖子中的代码。

更新:从现在开始,关于 JNA 结构中回调填充的讨论在 OP 最近发布更新后不再有效:https ://stackoverflow.com/revisions/e28dc30b-9b71-4d65-8f8a-cfc7a3d5231e/view -来源

根据给定的链接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 的接口或实现 Callback 的类中只有一个方法时,该方法的名称可能不是“回调”。参数和返回值遵循与直接函数调用相同的规则。

如果回调返回 String 或 String[],则返回的内存将有效,直到返回的对象被 GC'd。

以下是概述中的建议:

// 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按照规范维护其字段名称。您可能会也可能不会使用 null 值初始化它(我没有尝试过;可能它可能不起作用)。

更新:

似乎我上面的建议可以基于 tgdavies 在C 回调中与 JNA 无关的 JNA 解决方案起作用,这使得 JRE 崩溃,他填充了那些他对简单类型不感兴趣的回调字段,但匹配的回调字段名称在结构Callback中保持不变。sp_session_callbacks

我想,由于fuse_operations结构不当,fuse_main_real无法触发您感兴趣的预期熔断事件。

于 2012-01-17T08:12:08.623 回答