Использование библиотеки FUSE с Java; попытка повторить пример hello.c

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

Библиотека FUSE поставляется с несколькими файловыми системами примера, написанными на C. Простейшим из них является hello.c. Ниже приведена сводная информация о сводной версии кода для нескольких распечаток в функциях файловой системы:

hello.c:

/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <[email protected]>

  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 kernel 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 ': конечная точка транспорта не подключена`

Это потому, что Java-программа больше не работает, поэтому плавкий предохранитель не может вызвать свои обратные вызовы. Чтобы отключить, если я попробую 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 в качестве Callback вместо Pointer s. Однако поведение одинаковое.

Я посмотрел исходный код плавкого предохранителя, и код ошибки 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, и отключение завершено.

Ответ 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) ...

Таким образом, если вы дадите, если -f /some/mount/point в качестве списка аргументов, предохранитель попытается запустить:

/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 или связана с разрешениями. Я отредактирую заголовок вопроса и теги, чтобы это отразить. (В моей защите возвращался плагин с ошибкой ( "Permission denied" ), конечно, не помогло!)

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

Ответ 2

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

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

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

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

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

Примечание. Я предполагаю, что "ошибочная" основная 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 для этих полей пропущенных структур, вы удалили важную информацию о обратном вызове для каждого поля: его сигнатуру обратного вызова.

Из обзора API JNA:

Обратные вызовы (указатели функций)

JNA поддерживает доставку обратных вызовов Java на собственный код. Вы должны определить интерфейс, который расширяет интерфейс обратного вызова и определяет один метод обратного вызова с сигнатурой, которая соответствует указателю функции требуемый с помощью собственного кода. Имя метода может быть чем-то кроме "обратного вызова", только если в интерфейс, который расширяет Callback или класс, который реализует Перезвони. Аргументы и возвращаемое значение соответствуют тем же правилам, что и для вызов прямой функции.

Если обратный вызов возвращает строку или строку [], возвращенная память будет быть действительным до тех пор, пока возвращаемый объект не будет 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);

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

Возможно, вместо Pointer для каждого поля обратного вызова, которое вы хотите отложить, вы можете использовать поле Callback, сохранить его имя поля, как в спецификации. Вы можете или не можете инициализировать его нулевым значением (я этого не пробовал, возможно, он не работает).

Update:

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

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