Использование библиотеки FUSE с Java; пытается повторить пример hello.c - PullRequest
11 голосов
/ 15 января 2012

Я пытаюсь создать привязки к библиотеке FUSE, используя JNA , но я наткнулся на препятствие на дороге.Я максимально сократил код, чтобы сделать его удобочитаемым.

Библиотека 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() выполняется правильно.Я пытаюсь повторить то же самое в Java, используя JNA.Вот что я придумал:

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
  • FUSE 2.8.4
  • OpenJDK 1.6.0_23
  • JNA 3.4.0

Итак, мой вопрос: что именно отличается между этими двумя программами (hello.c и FuseTemp.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 ': конечная точка транспорта неconnected`

Это потому, что 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).

Теперь, запуская java-программу от имени root:

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

После этого, stat ing /some/mount/point показывает, что это не было изменено, то есть это все еще каталог 777.

Я также переписал FuseTemp.java, чтобы включить все Callback s как Callback s вместо Pointer s.Однако поведение такое же.

Я посмотрел исходный код fuse, и код ошибки 1 может быть возвращен в нескольких точках на протяжении всего выполнения.Я точно укажу, где именно происходит сбой на стороне предохранителя, и сообщу здесь.

Теперь для hello.c: запуск от имени обычного пользователя, запуск с такими же разрешениями на /some/mount/point и передача аргументов -f и /some/mount/point, программа сначала не печатает вывод, но продолжает работать.При запуске stat в точке монтирования программа печатает

getattr was called

, как и должно быть.stat возвращает ошибку, но это просто потому, что функция hello.c getattr не дает ей никакой информации, поэтому проблем нет.После выполнения fusermount -u /some/mount/point от имени обычного пользователя, программа завершается с кодом возврата 0, и размонтирование завершается успешно.

Запуск его от имени пользователя root, начиная с тех же разрешений для /some/mount/point и передавая ему аргументы -fи /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

Выполнение fusermount как root, программа завершается с кодом возврата 0, и размонтирование успешно.

Ответы [ 2 ]

7 голосов
/ 19 января 2012

Нашел это.Хотя в ретроспективе ошибка была действительно глупой, ее было нелегко обнаружить.

Решение : первый аргумент метода fuse_main_real в Fuse - это список аргументов.В этом списке ожидается, что аргумент 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" и будет жаловаться.

Как это было найдено : Добавление группы printf() в исходный код предохранителя, чтобы выяснить, где именно происходит сбой: в /lib/mount_util.c в строке 82:

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

Я прошу прощения за то, что предположил, что ошибка произошла из-за того, что она связана с Java, JNA или разрешениями.Я отредактирую заголовок вопроса и теги, чтобы отразить это.(В мою защиту возвращался предохранитель ошибки («Отказано в доступе»), безусловно, не помогло!)

Спасибо за помощь, э-э.и техномаг, и еще раз прошу прощения за то, что отнял у вас часть времени из-за того, что оказалось глупой ошибкой.

2 голосов
/ 17 января 2012

Относительно проблемы отказа в разрешении при запуске jar ... Я уверен, что здесь происходит разрешение безопасности Java, чтобы объяснить, почему не возникает исключение при работе в режиме суперпользователя, но исключение с отказом в разрешении обнаруживается при работе в неРежим суперпользователя.

Из того, что я могу понять, Java имеет уровень безопасности в отличие от стандартной программы на C (за исключением некоторых библиотек C, которые могут включать в себя проверки безопасности, например, библиотеки C ++, управляемые .NET).Несмотря на то, что функции манипулирования файлами происходят из libfuse.so, он также может вызывать системные вызовы Linux, которые могут выполняться в пространстве памяти системного ядра.Поскольку теперь он работает через Java, где Java должна загружать / отображать все функции библиотеки, включая системные вызовы, в память.Если Java обнаруживает, что карта памяти находится в области памяти ядра системы, а не в области памяти пользователя во время выполнения, она будет обращаться к своему диспетчеру безопасности для проверки текущего состояния пользователя программы Java.

В противном случае ошибка отказа в доступе может фактически произойти из-за предохранителя, пытающегося получить доступ к точке монтирования, которая ограничена обычным пользователем, что является ожидаемым поведением.Тогда это не имеет ничего общего с Java.Но эта ошибка также должна возникать в программе на Си.Но, судя по вашему посту и комментариям, это мало что говорит.

Однако запуск программы от имени root не привел к появлению ошибки.Увы, он, похоже, ничего не делал: он просто сказал «Монтирование» и «Монтирование» мгновенно.Таким образом, он подходит к завершению, но вызов fuse_main_real возвращается мгновенно.Возвращаемое число равно 1. Это некоторый прогресс, но программа должна быть запущена как обычный пользователь, такой как hello.c.

С другой стороны, основываясь на вашем последнем комментарии выше,кажется, что ваши поля указателя функции (обратного вызова) в структуре StructFuseOperations не работают для "запуска" любого события fuse, которое может вызвать fuse.

Примечание: Я предполагаю, что "ошибочная "основная Java-программа отображает" Mounting "и" Mounting "и ничего другого между ними, что на самом деле включает вызов метода fuse_main_real, который не вызывает событие fuse, но возвращает код 1 при запуске программы врежим суперпользователя.Я не пробовал код в посте, так как у меня нет доступа к ОС Linux прямо сейчас.

Обновление : начиная с этого момента, обсуждение заполнения отступов в JNAструктура больше не действительна после недавнего пост-обновления, сделанного OP: https://stackoverflow.com/revisions/e28dc30b-9b71-4d65-8f8a-cfc7a3d5231e/view-source

Исходя из указанной ссылки, fuse_operations Ссылка на структуру , вы сосредотачиваетесь только на нескольких полях структуры 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'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,сохранить его имя поля, как в спецификации.Вы можете или не можете инициализировать его с нулевым значением (я не пробовал это; возможно, это может не работать).

Обновление:

Кажется, что мойприведенное выше предложение может работать на основе несвязанного решения JNA от tgdavies в C обратного вызова с JNA приводит к сбою JRE , когда он дополняет те поля обратного вызова, которые его не интересуют простым типом Callback, но совпадающие имена полей обратного вызова остаютсянетронутыми в структуре sp_session_callbacks.

Я думаю, из-за неправильной структуры fuse_operations, fuse_main_real не может запустить ожидаемое событие предохранителя, которое вас интересует.

...