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()

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

私の質問は、これら 2 つのプログラム (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/mtabchmod'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 ディレクトリです。

また、すべての s をs ではなくs としてFuseTemp.java含めるように書き直しました。ただし、動作は同じです。CallbackCallbackPointer

ヒューズのソース コードを調べたところ、実行中の複数の時点でエラー コード 1 が返される可能性があります。ヒューズ側のどこが故障しているかを正確に特定し、ここに報告します。

現在、hello.c通常のユーザーとして実行し、同じ権限で開始し、引数と/some/mount/pointを渡すと、プログラムは最初は出力を出力しませんが、実行を続けます。マウントポイントで実行すると、プログラムは出力します-f/some/mount/pointstat

getattr was called

あるべきように。statはエラーを返しますが、それは単にhello.cgetattr関数が情報を提供しないためであり、問​​題はありません。通常のユーザーとして実行した後fusermount -u /some/mount/point、プログラムはリターン コード 0 で終了し、アンマウントは成功します。

root として実行し、同じパーミッション on から開始して引数and/some/mount/pointを渡すと、プログラムは最初は出力を出力しませんが、実行を続けます。マウントポイントで実行すると、root ではないため、権限エラーが発生します。ルートとして実行すると、プログラムは出力します-f/some/mount/pointstatstat

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

それを見つけた。振り返ってみると、このエラーは本当にばかげていましたが、見つけるのは簡単ではありませんでした。

解決策: Fuse のfuse_main_realメソッドの最初の引数は引数リストです。このリストでは、引数 0 がファイル システム名または何らかの意味のあるプログラム名であると想定しています。したがって、代わりに

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

だったはず

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

mainこれは、プログラム名も含まれていないため、Java がメソッドで提供する引数リストを使用できないことも意味します。

重要な理由: ヒューズは実際には独自の引数の解析を行い/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不平を言うでしょう。

発見方法: 82 行目printf()で、正確にどこで問題が発生したかを把握するために、fuse のソース コード内に一連のコードを追加します。/lib/mount_util.c

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 プログラムとは異なり、セキュリティ層があります (ただし、.NET マネージド C++ ライブラリのように、セキュリティ チェックを含む可能性がある一部の C ライブラリは除きます)。ファイル操作関数は から来ていますが、libfuse.soシステム カーネル メモリ空間内で実行される可能性がある Linux システム カーネル コールも呼び出す可能性があります。現在、Java を介して実行されているため、Java はシステム コールを含むすべてのライブラリ関数をメモリにロード/マップする必要があります。Java は、実行中にメモリ マップがユーザー メモリ空間ではなくシステム カーネル メモリ空間で発生することを検出すると、セキュリティ マネージャを参照して、Java プログラムの現在のユーザ状態をチェックします。

そうしないと、通常のユーザーから制限されているマウント ポイントにヒューズがアクセスしようとしているために、許可が拒否されたというエラーが実際に発生する可能性があります。これは、予期される動作です。それから、これは Java とは関係ありません。ただし、このエラーは C プログラムでも同様に発生します。しかし、あなたの投稿とコメントからは、それほど多くはわかりません。

ただし、ルートとしてプログラムを実行しても、エラーは発生しませんでした。残念ながら、何もしていないように見えました。「マウント中」と「マウント済み」と即座に表示されました。したがって、完了まで進みますが、fuse_main_real 呼び出しは即座に戻ります。返される数値は 1 です。これはある程度の進歩ですが、hello.c のように通常のユーザーが実行できるようにプログラムを実行できる必要があります。

一方、上記の最近のコメントに基づいて、StructFuseOperations構造内の関数ポインター(コールバック)フィールドは、ヒューズが呼び出す可能性のあるヒューズイベントを「起動」するために機能していないようです。

注:「誤った」メインJavaプログラムが「Mounting」と「Mounted」を表示し、その間に他に何も表示しないと仮定します。これには、実際にはfuse_main_realヒューズイベントを起動しないメソッドの呼び出しが含まれますが、実行時に1の戻りコードが発生しますスーパーユーザーモードでプログラム。現在 Linux OS にアクセスできないため、投稿のコードは試していません。

更新: この時点から、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 を実装するクラスに単一のメソッドしかない場合にのみ、「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たい各コールバック フィールドの代わりに、field を使用Callbackして、そのフィールド名を仕様どおりに維持できます。null値で初期化する場合としない場合があります(これは試していません。おそらく機能しない可能性があります)。

アップデート:

上記の私の提案は、JNA を使用したC コールバックのtgdavies による無関係な JNA ソリューションに基づいて機能する可能性があるようです。JRE がクラッシュし、興味のないコールバック フィールドを単純な型でパディングしましたが、一致するコールバック フィールド名は構造Callback内にそのまま残りました。sp_session_callbacks

不適切なfuse_operations構造のために、fuse_main_realあなたが興味を持っていると予想されるヒューズイベントを起動できないと思います。

于 2012-01-17T08:12:08.623 に答える