Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ env:

jobs:
linux:
name: Test jfuse-linux packages
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: x86_64
fuseLibPath: /usr/lib/x86_64-linux-gnu/libfuse3.so.3
- os: ubuntu-24.04-arm
arch: aarch64
fuseLibPath: /usr/lib/aarch64-linux-gnu/libfuse3.so.3
runs-on: ${{ matrix.os }}
name: Test jfuse-linux-${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
Expand All @@ -31,7 +34,7 @@ jobs:
sudo apt-get update
sudo apt-get install fuse3 libfuse3-dev
- name: Maven build
run: mvn -B verify -Dfuse.lib.path="/lib/${{ matrix.arch }}-linux-gnu/libfuse3.so.3" --no-transfer-progress
run: mvn -B verify -Dfuse.lib.path="${{ matrix.fuseLibPath }}" --no-transfer-progress
- uses: actions/upload-artifact@v4
with:
name: coverage-linux-${{ matrix.arch }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,19 @@
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;

/**
* Class for not jextract'able symbols, e.g. fuse_new_31
* Class for not jextract'able versioned symbols, e.g. fuse_new@FUSE_3.0
*/
public class FuseFFIHelper {


static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
.or(Linker.nativeLinker().defaultLookup());

static MemorySegment findOrThrow(String symbol) {
return SYMBOL_LOOKUP.find(symbol)
return FuseSymbolLookup.getInstance().find(symbol)
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol));
}

private static class fuse_new_31 {
private static class fuse_new {
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
fuse_h.C_POINTER,
fuse_h.C_POINTER,
Expand All @@ -32,38 +27,17 @@ private static class fuse_new_31 {
);

public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(
findOrThrow("fuse_new_31"),
findOrThrow("fuse_new@FUSE_3.0"),
DESC);
}

/**
* Function descriptor for:
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static FunctionDescriptor fuse_new_31$descriptor() {
return fuse_new_31.DESC;
}


/**
* Downcall method handle for:
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static MethodHandle fuse_new_31$handle() {
return fuse_new_31.HANDLE;
}

/**
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static MemorySegment fuse_new_31(MemorySegment args, MemorySegment op, long op_size, MemorySegment private_data) {
var mh$ = fuse_new_31.HANDLE;
public static MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size, MemorySegment private_data) {
var mh$ = fuse_new.HANDLE;
try {
return (MemorySegment) mh$.invokeExact(args, op, op_size, private_data);
} catch (Throwable ex$) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected FuseMount mount(List<String> args) throws FuseMountFailedException {

@VisibleForTesting
MemorySegment createFuseFS(FuseArgs fuseArgs) throws FuseMountFailedException {
var fuse = FuseFFIHelper.fuse_new_31(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL);
var fuse = FuseFFIHelper.fuse_new(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL);
if (MemorySegment.NULL.equals(fuse)) {
throw new FuseMountFailedException("fuse_new failed");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.cryptomator.jfuse.linux.aarch64;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;

public class FuseSymbolLookup implements SymbolLookup {

private static final int RTLD_NOW = 0x02;

// https://man7.org/linux/man-pages/man3/dlopen.3.html
private static final FunctionDescriptor DLOPEN = FunctionDescriptor.of(ADDRESS, ADDRESS, JAVA_INT);
private static final FunctionDescriptor DLERROR = FunctionDescriptor.of(ADDRESS);

// https://man7.org/linux/man-pages/man3/dlvsym.3.html
private static final FunctionDescriptor DLVSYM = FunctionDescriptor.of(ADDRESS, ADDRESS, ADDRESS, ADDRESS);
private static final FunctionDescriptor DLSYM = FunctionDescriptor.of(ADDRESS, ADDRESS, ADDRESS);

private final MethodHandle dlopen;
private final MethodHandle dlerror;
private final MethodHandle dlvsym;
private final MethodHandle dlsym;
private final AtomicReference<MemorySegment> libHandle = new AtomicReference<>();

private FuseSymbolLookup() {
var linker = Linker.nativeLinker();
var defaultLookup = linker.defaultLookup();
this.dlopen = linker.downcallHandle(defaultLookup.find("dlopen").orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol dlopen")), DLOPEN);
this.dlerror = linker.downcallHandle(defaultLookup.find("dlerror").orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol dlerror")), DLERROR);
this.dlvsym = linker.downcallHandle(defaultLookup.find("dlvsym").orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol dlvsym")), DLVSYM);
this.dlsym = linker.downcallHandle(defaultLookup.find("dlsym").orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol dlsym")), DLSYM);
}

public static FuseSymbolLookup getInstance() {
return Holder.INSTANCE;
}

private static class Holder {
private static final FuseSymbolLookup INSTANCE = new FuseSymbolLookup();
}

public void open(String libPath) {
try (var session = Arena.ofConfined()) {
MemorySegment handle = (MemorySegment) dlopen.invokeExact(session.allocateFrom(libPath), RTLD_NOW);
libHandle.set(handle);
} catch (Throwable e) {
throw new AssertionError(e);
}
}
Comment on lines +50 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding dlclose functionality or clarifying lifecycle.
Opening the library without closing may lead to resource leaks over long uptimes. If you expect to reload or unload the library at any point, implementing a matching dlclose call or clarifying that this process is intentionally omitted would be beneficial.


/**
* {@inheritDoc}
*
* @param nameAndVersion the symbol name and version separated by '@'.
*/
@Override
public Optional<MemorySegment> find(String nameAndVersion) {
var handle = libHandle.get();
if (handle == null) {
return Optional.empty();
}
var sep = nameAndVersion.indexOf('@');
try (var session = Arena.ofConfined()) {
MemorySegment addr = MemorySegment.NULL;
if (sep != -1) {
String name = nameAndVersion.substring(0, sep);
String version = nameAndVersion.substring(sep + 1);
addr = (MemorySegment) dlvsym.invokeExact(handle, session.allocateFrom(name), session.allocateFrom(version));
}

if (MemorySegment.NULL.equals(addr)) {
addr = (MemorySegment) dlsym.invokeExact(handle, session.allocateFrom(nameAndVersion));
}

if (MemorySegment.NULL.equals(addr)) {
var error = (MemorySegment) dlerror.invokeExact();
System.err.println("dlvsym failed for symbol " + nameAndVersion + ": " + error.getString(0));
return Optional.empty();
} else {
return Optional.of(addr);
}
} catch (Throwable e) {
return Optional.empty();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public void setLibraryPath(String libraryPath) {
@Override
public Fuse build(FuseOperations fuseOperations) throws UnsatisfiedLinkError {
if (libraryPath != null) {
FuseSymbolLookup.getInstance().open(libraryPath);
System.load(libraryPath);
} else {
System.loadLibrary(DEFAULT_LIBNAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@
import org.cryptomator.jfuse.api.FuseMountFailedException;
import org.cryptomator.jfuse.api.FuseOperations;
import org.cryptomator.jfuse.api.TimeSpec;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3_lowlevel.fuse_cmdline_opts;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_config;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_conn_info;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_file_info;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_h;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.timespec;
import org.junit.jupiter.api.AfterEach;
import org.cryptomator.jfuse.linux.aarch64.extr.fuse3_lowlevel.fuse_cmdline_opts;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Answers;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.lang.foreign.Arena;
Expand All @@ -45,7 +42,7 @@ public class Mount {
@DisplayName("MountFailedException when fuse_new fails")
public void testFuseNewFails() {
try (var fuseH = Mockito.mockStatic(FuseFFIHelper.class)) {
fuseH.when(() -> FuseFFIHelper.fuse_new_31(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.any())).thenReturn(MemorySegment.NULL);
fuseH.when(() -> FuseFFIHelper.fuse_new(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.any())).thenReturn(MemorySegment.NULL);
var thrown = Assertions.assertThrows(FuseMountFailedException.class, () -> fuseImplSpy.createFuseFS(Mockito.mock(FuseArgs.class)));
Assertions.assertEquals("fuse_new failed", thrown.getMessage());
}
Expand Down
20 changes: 19 additions & 1 deletion jfuse-linux-amd64/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
<cPreprocessorMacros>
<cPreprocessorMacro>_FILE_OFFSET_BITS=64</cPreprocessorMacro>
<cPreprocessorMacro>FUSE_USE_VERSION=317</cPreprocessorMacro>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeFunctions>
<includeFunction>fuse_version</includeFunction>
Expand Down Expand Up @@ -144,6 +146,8 @@
<cPreprocessorMacros>
<cPreprocessorMacro>_FILE_OFFSET_BITS=64</cPreprocessorMacro>
<cPreprocessorMacro>FUSE_USE_VERSION=317</cPreprocessorMacro>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeStructs>
<includeStruct>fuse_cmdline_opts</includeStruct>
Expand All @@ -159,6 +163,10 @@
<headerFile>${linux.headerSearchPath}/errno.h</headerFile>
<targetPackage>org.cryptomator.jfuse.linux.amd64.extr.errno</targetPackage>
<headerClassName>errno_h</headerClassName>
<cPreprocessorMacros>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeConstants>
<includeConstant>ENOENT</includeConstant>
<includeConstant>ENOSYS</includeConstant>
Expand Down Expand Up @@ -190,6 +198,10 @@
<headerFile>${linux.headerSearchPath}/sys/stat.h</headerFile>
<targetPackage>org.cryptomator.jfuse.linux.amd64.extr.stat</targetPackage>
<headerClassName>stat_h</headerClassName>
<cPreprocessorMacros>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeConstants>
<includeConstant>UTIME_NOW</includeConstant>
<includeConstant>UTIME_OMIT</includeConstant>
Expand All @@ -203,8 +215,12 @@
</goals>
<configuration>
<headerFile>${linux.headerSearchPath}/fcntl.h</headerFile>
<headerClassName>fcntl_h</headerClassName>
<targetPackage>org.cryptomator.jfuse.linux.amd64.extr.fcntl</targetPackage>
<headerClassName>fcntl_h</headerClassName>
<cPreprocessorMacros>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeConstants>
<includeConstant>O_RDONLY</includeConstant>
<includeConstant>O_WRONLY</includeConstant>
Expand All @@ -229,6 +245,8 @@
<headerClassName>stdio_h</headerClassName>
<cPreprocessorMacros>
<cPreprocessorMacro>_GNU_SOURCE=1</cPreprocessorMacro>
<cPreprocessorMacro>__x86_64__=1</cPreprocessorMacro>
<cPreprocessorMacro>__LP64__=1</cPreprocessorMacro>
</cPreprocessorMacros>
<includeConstants>
<!-- GNU-specific flags for rename: -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,19 @@
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;

/**
* Class for not jextract'able symbols, e.g. fuse_new_31
* Class for not jextract'able versioned symbols, e.g. fuse_new@FUSE_3.0
*/
public class FuseFFIHelper {


static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
.or(Linker.nativeLinker().defaultLookup());

static MemorySegment findOrThrow(String symbol) {
return SYMBOL_LOOKUP.find(symbol)
return FuseSymbolLookup.getInstance().find(symbol)
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol));
}

private static class fuse_new_31 {
private static class fuse_new {
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
fuse_h.C_POINTER,
fuse_h.C_POINTER,
Expand All @@ -32,38 +27,17 @@ private static class fuse_new_31 {
);

public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(
findOrThrow("fuse_new_31"),
findOrThrow("fuse_new@FUSE_3.0"),
DESC);
}

/**
* Function descriptor for:
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static FunctionDescriptor fuse_new_31$descriptor() {
return fuse_new_31.DESC;
}


/**
* Downcall method handle for:
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static MethodHandle fuse_new_31$handle() {
return fuse_new_31.HANDLE;
}

/**
* {@snippet lang = c:
* struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *private_data)
*}
*/
public static MemorySegment fuse_new_31(MemorySegment args, MemorySegment op, long op_size, MemorySegment private_data) {
var mh$ = fuse_new_31.HANDLE;
public static MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size, MemorySegment private_data) {
var mh$ = fuse_new.HANDLE;
try {
return (MemorySegment) mh$.invokeExact(args, op, op_size, private_data);
} catch (Throwable ex$) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected FuseMount mount(List<String> args) throws FuseMountFailedException {

@VisibleForTesting
MemorySegment createFuseFS(FuseArgs fuseArgs) throws FuseMountFailedException {
var fuse = FuseFFIHelper.fuse_new_31(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL);
var fuse = FuseFFIHelper.fuse_new(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL);
if (MemorySegment.NULL.equals(fuse)) {
throw new FuseMountFailedException("fuse_new failed");
}
Expand Down
Loading