【问题标题】:Using FUSE library with Java; trying to replicate hello.c example在 Java 中使用 FUSE 库;试图复制 hello.c 示例
【发布时间】:2012-01-15 19:53:42
【问题描述】:

我正在尝试使用 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.

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

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

jna.jar is available here.

出现的错误是: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表示没有被修改过,即仍然是777目录。

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

我查看了 fuse 的源代码,错误代码 1 可以在整个执行过程中的多个点返回。我会查明保险丝端到底是哪里出了故障,然后在这里报告。

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

getattr was called

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

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

getattr was called

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

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

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

【问题讨论】:

  • 不知道,但有趣的是看到 jar 发出 permission denied 错误。如果在java命令之前使用linux命令su,是否有效?
  • 您是否将系统设置为允许使用 linux chmod 命令执行您的 jar 文件?
  • 在 jna.jar 上使用 chmod +x 什么也没做(为什么会这样?Java 只需要读取它)。但是,以 root 身份运行程序不会导致出现错误。唉,它似乎什么也没做:它只是立即说“Mounting”和“Mounted”。所以它确实会完成,但fuse_main_real 调用会立即返回。它返回的数字是1。这是一些进步,但程序需要像 hello.c 这样的普通用户可以运行。
  • 其实我问这样的问题是为了证实我的怀疑。当涉及到文件系统调用时,Java 应用程序似乎正在使用其默认的安全权限模型,在 Linux 下,只有 root/系统用户帐户可以与文件系统调用交互以修改文件系统。非 root/系统用户将获得权限被拒绝或出现相同的对话框。在 Windows Vista/7 中,如果您不在管理员组中,它将触发 UAC。
  • 您的 Java JNA 程序似乎仅在超级用户模式下运行时才有效,尽管返回错误为 1。因此,您有机会改进/纠正您的 JNA 代码以解决错误超级用户模式。

标签: java c arguments mount fuse


【解决方案1】:

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

解决方案: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) ...

因此,如果您将 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”,会抱怨。

它是如何被发现的:在 fuse 的源代码中添加一堆 printf() 以确定失败的确切位置:在第 82 行的 /lib/mount_util.c 中:

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

对于假设错误是由于它与 Java 相关或 JNA 相关或权限相关,我深表歉意。我将编辑问题标题和标签以反映这一点。 (在我的辩护中,错误熔断器正在返回(“Permission denied”)当然没有帮助!)

感谢您的帮助。和 technomage,我再次道歉,因为结果证明是一个愚蠢的错误,占用了你的大部分时间。

【讨论】:

  • 没问题,不过……分享很贴心:)
【解决方案2】:

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

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

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

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

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

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

更新:从现在开始,关于 JNA 结构中回调填充的讨论在 OP 最近发布更新后不再有效: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 接口的接口,并定义一个 具有与函数指针匹配的签名的回调方法 本机代码所需的。方法的名称可能是什么 仅当只有一个方法时才使用“回调” 扩展回调的接口或实现的类 打回来。参数和返回值遵循与 for 相同的规则 直接函数调用。

如果回调返回一个字符串或字符串[],返回的内存将 在返回的对象被 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);

但是,它并不建议在结构中使用填充技术正确跳过回调,尤其是当它太大并且您不想定义您不感兴趣的每个回调时。也许,这是没有保证的,并且可能会导致像您所面临的那样未定义的行为......

可能,对于要填充的每个回调字段,您可以使用 Callback 字段,而不是 Pointer,按照规范维护其字段名称。您可能会也可能不会使用 null 值初始化它(我没有尝试过;可能它可能不起作用)。

更新:

似乎我上面的建议可以基于 tgdavies 在C callback with JNA makes JRE crash 中的不相关 JNA 解决方案起作用,他在其中填充了他对简单的 Callback 类型不感兴趣的回调字段,但匹配的回调字段名称在sp_session_callbacks 结构。

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

【讨论】:

  • 实际上我有一个完整的结构,一开始没有任何填充或空回调,但仍然存在这个问题。我已将它们修剪为填充指针,以使代码在 stackoverflow 上更易于消化。我已经编辑了问题以具有所有回调的完整结构。其中一些是空的(那些没有在 hello.c 中定义的),但是当它们都被定义时的行为是相同的(这些回调中没有任何东西被打印出来)。还添加了输出和权限等等。
  • @EtiennePerot 感谢您添加更多信息(正确的输出示例)
  • 对字段使用指针或回调是不相关的。 相关的是,所有 字段必须在 Java 对象中定义,并且它们的对齐方式、偏移量和整体结构大小必须与本机代码匹配。回调签名必须匹配的唯一情况是当您要为其分配 Java 对象或打算从 Java 调用本机指定的回调时。
  • @EtiennePerot 尝试点:(1)首先,通过将null 传递给fuse_main_real() 中的StructFuseOperations 参数来禁用所有回调,以便我们可以判断保险丝是否工作(仍然返回错误代码1?); (2) 然后,尝试激活结构中的第一个回调,getatrr。如果它有效,那么我们可以一个一个地激活其余的回调。 (3) 对于以struct * 为参数的参数,我会更喜欢您是否可以完成它们的实际结构声明,而不仅仅是一个指针。
  • 使用null 作为参数给出了与第一个问题中描述的完全相同的结果。它一定是比这更基本的东西......我会继续寻找。
猜你喜欢
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多