This is an automated email from the ASF dual-hosted git repository.
lhotari pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
The following commit(s) were added to refs/heads/master by this push:
new edf2155563 feat: migrate native-io implementation from C to Rust
(#4738)
edf2155563 is described below
commit edf21555638800c31bb3b8d6c72d18c39a4ff056
Author: Zixuan Liu <[email protected]>
AuthorDate: Wed Apr 15 23:41:49 2026 +0800
feat: migrate native-io implementation from C to Rust (#4738)
* feat: migrate native-io implementation from C to Rust
* fix: use Apache Commons SystemUtils for OS name and architecture retrieval
* feat: add support for pure Rust native-io builds with conditional
activation
* chore: update Rust and Zig toolchain versions
* chore: extract native build setup action
Co-authored-by: Copilot <[email protected]>
* fix: add shell to native build action
Co-authored-by: Copilot <[email protected]>
---------
Co-authored-by: Copilot <[email protected]>
---
.github/actions/setup-native-build/action.yml | 38 +++
.github/workflows/bk-ci.yml | 22 ++
.github/workflows/bk-streamstorage-python.yml | 4 +-
.github/workflows/codeql.yml | 4 +
.github/workflows/java21-daily-build.yml | 2 +
.github/workflows/windows-daily-build.yml | 2 +
native-io/pom.xml | 281 ++++++++---------
native-io/src/main/assembly/assembly.xml | 62 ----
.../common/util/nativeio/NativeIOJni.java | 35 ++-
.../common/util/nativeio/NativeIOLibraryPath.java | 124 ++++++++
.../src/main/native-io-jni/cpp/native_io_jni.c | 325 -------------------
native-io/src/main/native-io-jni/rust/Cargo.lock | 301 ++++++++++++++++++
native-io/src/main/native-io-jni/rust/Cargo.toml | 41 +++
native-io/src/main/native-io-jni/rust/build.rs | 37 +++
native-io/src/main/native-io-jni/rust/src/lib.rs | 346 +++++++++++++++++++++
.../util/nativeio/NativeIOLibraryPathTest.java | 100 ++++++
pom.xml | 3 +
17 files changed, 1185 insertions(+), 542 deletions(-)
diff --git a/.github/actions/setup-native-build/action.yml
b/.github/actions/setup-native-build/action.yml
new file mode 100644
index 0000000000..4c9c234c5c
--- /dev/null
+++ b/.github/actions/setup-native-build/action.yml
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+name: setup native build
+description: sets up Rust, Zig, and cargo-zigbuild for native builds
+runs:
+ using: composite
+ steps:
+ - name: Set up Rust
+ uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
+ with:
+ # Cross-compilation targets: Linux amd64 and arm64 (glibc)
+ targets: >-
+ x86_64-unknown-linux-gnu,
+ aarch64-unknown-linux-gnu
+
+ - name: Set up Zig
+ uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29
+
+ - name: Install cargo-zigbuild
+ run: cargo install --locked cargo-zigbuild
+ shell: bash
diff --git a/.github/workflows/bk-ci.yml b/.github/workflows/bk-ci.yml
index 64116a3fe6..582d9589b6 100644
--- a/.github/workflows/bk-ci.yml
+++ b/.github/workflows/bk-ci.yml
@@ -82,6 +82,10 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ if: steps.check_changes.outputs.docs_only != 'true'
+ uses: ./.github/actions/setup-native-build
+
- name: Validate pull request
if: steps.check_changes.outputs.docs_only != 'true'
run: |
@@ -161,6 +165,9 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: Tune Java DNS TTL settings
run: |
sudo tee -a $JAVA_HOME/conf/security/java.security <<EOF
@@ -244,6 +251,9 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: Tune Java DNS TTL settings
run: |
sudo tee -a $JAVA_HOME/conf/security/java.security <<EOF
@@ -341,6 +351,9 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: Restore released versions cache
uses: actions/cache/restore@v4
with:
@@ -437,6 +450,9 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: mvn package
run: mvn -B -nsu clean package -DskipTests
@@ -471,6 +487,9 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: mvn package
run: mvn -B -nsu clean package -DskipTests
@@ -514,6 +533,9 @@ jobs:
distribution: 'temurin'
java-version: ${{ matrix.jdk_version }}
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
+
- name: Build with Maven
run: mvn clean package -B -nsu -DskipBookKeeperServerTests
diff --git a/.github/workflows/bk-streamstorage-python.yml
b/.github/workflows/bk-streamstorage-python.yml
index 097b18a9b4..5e696e052a 100644
--- a/.github/workflows/bk-streamstorage-python.yml
+++ b/.github/workflows/bk-streamstorage-python.yml
@@ -73,6 +73,8 @@ jobs:
with:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
- name: Build
run: mvn -q -T 1C -B -nsu clean install -DskipTests -Dcheckstyle.skip
-Dspotbugs.skip -Drat.skip -Dmaven.javadoc.skip
- name: Pick ubuntu mirror for the docker image build
@@ -83,5 +85,3 @@ jobs:
run: ./stream/clients/python/docker/build-local-image.sh
- name: Test
run: ./stream/clients/python/scripts/docker_integration_tests.sh
-
-
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 156468c5f3..ea7da56263 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -85,6 +85,10 @@ jobs:
distribution: 'temurin'
java-version: 17
+ - name: Setup native build
+ if: steps.check_changes.outputs.docs_only != 'true'
+ uses: ./.github/actions/setup-native-build
+
- name: Validate pull request
if: steps.check_changes.outputs.docs_only != 'true'
run: |
diff --git a/.github/workflows/java21-daily-build.yml
b/.github/workflows/java21-daily-build.yml
index 331ad9b61c..961d60b14d 100644
--- a/.github/workflows/java21-daily-build.yml
+++ b/.github/workflows/java21-daily-build.yml
@@ -42,6 +42,8 @@ jobs:
with:
distribution: 'temurin'
java-version: 21
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
- name: Build with Maven
run: mvn -B clean install
- name: Aggregates all test reports to ./test-reports and
./surefire-reports directories If failure
diff --git a/.github/workflows/windows-daily-build.yml
b/.github/workflows/windows-daily-build.yml
index 513c28241b..25035b49c7 100644
--- a/.github/workflows/windows-daily-build.yml
+++ b/.github/workflows/windows-daily-build.yml
@@ -43,6 +43,8 @@ jobs:
with:
distribution: 'temurin'
java-version: 21
+ - name: Setup native build
+ uses: ./.github/actions/setup-native-build
- name: Build with Maven
run: mvn -B clean install
- name: Aggregates all test reports to ./test-reports and
./surefire-reports directories If failure
diff --git a/native-io/pom.xml b/native-io/pom.xml
index 923ea43dbe..fe3d15820b 100644
--- a/native-io/pom.xml
+++ b/native-io/pom.xml
@@ -24,13 +24,19 @@
</parent>
<artifactId>native-io</artifactId>
- <packaging>nar</packaging>
+ <packaging>jar</packaging>
<name>Apache BookKeeper :: Native IO Library</name>
<description>Native IO Library</description>
<properties>
- <nar.runtime>dynamic</nar.runtime>
- <nar.cpp.optionSet>-msse4.2 -mpclmul</nar.cpp.optionSet>
+ <rust.dir>${project.basedir}/src/main/native-io-jni/rust</rust.dir>
+ <rust.target.dir>${rust.dir}/target</rust.target.dir>
+ <rust.executable>cargo</rust.executable>
+
<rust.release.lib.relative.path>release/libnative_io.so</rust.release.lib.relative.path>
+
<rust.zig.target.amd64.gnu>x86_64-unknown-linux-gnu</rust.zig.target.amd64.gnu>
+
<rust.zig.target.arm64.gnu>aarch64-unknown-linux-gnu</rust.zig.target.arm64.gnu>
+ <rust.lib.dir>${project.build.directory}/classes/lib</rust.lib.dir>
+ <native.io.pure.rust>false</native.io.pure.rust>
</properties>
<dependencies>
@@ -43,6 +49,12 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -51,175 +63,156 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>com.github.maven-nar</groupId>
- <artifactId>nar-maven-plugin</artifactId>
- <extensions>true</extensions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <configuration>
- <descriptors>
- <descriptor>src/main/assembly/assembly.xml</descriptor>
- </descriptors>
- <appendAssemblyId>false</appendAssemblyId>
- <tarLongFileMode>posix</tarLongFileMode>
- </configuration>
- <executions>
- <execution>
- <id>make-assembly</id>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>**/rust/target/**</exclude>
+ </excludes>
+ </configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
- <!-- from JDK10 javah command is not available
- see http://openjdk.java.net/jeps/313
+ <!--
+ Cross-compile for Linux amd64 + arm64 (glibc) via cargo-zigbuild.
+ Produces a single JAR containing two variants:
+ lib/linux-x86_64-gnu/libnative-io.so (glibc, amd64)
+ lib/linux-aarch64-gnu/libnative-io.so (glibc, arm64)
+
+ Prerequisites: zig, cargo-zigbuild, and both Rust targets
+ added via `rustup target add`.
-->
- <id>jdk-without-javah</id>
+ <id>cargo-zigbuild</id>
<activation>
- <jdk>[10,)</jdk>
+ <property>
+ <name>native.io.pure.rust</name>
+ <value>!true</value>
+ </property>
</activation>
<build>
<plugins>
<plugin>
- <groupId>com.github.maven-nar</groupId>
- <artifactId>nar-maven-plugin</artifactId>
- <extensions>true</extensions>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
<executions>
- <execution>
- <!-- javah is not present in JDK10 onwards,
- you have to to use javac -h -->
- <id>default-nar-javah</id>
- <phase>none</phase>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <configuration>
- <compilerArgs>
- <!-- add -h flag to javac -->
- <compilerArg>-h</compilerArg>
-
<compilerArg>${project.build.directory}/nar/javah-include</compilerArg>
- </compilerArgs>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
- <id>mac</id>
- <activation>
- <os>
- <name>Mac OS X</name>
- </os>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>com.github.maven-nar</groupId>
- <artifactId>nar-maven-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <runtime>${nar.runtime}</runtime>
- <output>native-io</output>
- <libraries>
- <library>
- <type>jni</type>
-
<narSystemPackage>org.apache.bookkeeper.util.nativeio</narSystemPackage>
- </library>
- </libraries>
- <cpp>
- <optionSet>${nar.cpp.optionSet}</optionSet>
- <exceptions>false</exceptions>
- <rtti>false</rtti>
- <optimize>full</optimize>
- </cpp>
- </configuration>
+ <execution>
+ <id>cargo-zigbuild-amd64-gnu</id>
+ <phase>compile</phase>
+ <goals><goal>exec</goal></goals>
+ <configuration>
+ <executable>${rust.executable}</executable>
+ <workingDirectory>${rust.dir}</workingDirectory>
+ <arguments>
+ <argument>zigbuild</argument>
+ <argument>--release</argument>
+ <argument>--target</argument>
+ <argument>${rust.zig.target.amd64.gnu}</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>cargo-zigbuild-arm64-gnu</id>
+ <phase>compile</phase>
+ <goals><goal>exec</goal></goals>
+ <configuration>
+ <executable>${rust.executable}</executable>
+ <workingDirectory>${rust.dir}</workingDirectory>
+ <arguments>
+ <argument>zigbuild</argument>
+ <argument>--release</argument>
+ <argument>--target</argument>
+ <argument>${rust.zig.target.arm64.gnu}</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
- </plugins>
- </build>
- </profile>
-
- <profile>
- <id>Linux</id>
- <activation>
- <os>
- <name>Linux</name>
- </os>
- </activation>
- <build>
- <plugins>
<plugin>
- <groupId>com.github.maven-nar</groupId>
- <artifactId>nar-maven-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <runtime>${nar.runtime}</runtime>
- <output>native-io</output>
- <libraries>
- <library>
- <type>jni</type>
-
<narSystemPackage>org.apache.bookkeeper.util.nativeio</narSystemPackage>
- </library>
- </libraries>
- <cpp>
- <optionSet>${nar.cpp.optionSet}</optionSet>
- <exceptions>false</exceptions>
- <rtti>false</rtti>
- <optimize>full</optimize>
- </cpp>
- </configuration>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-zig-libs</id>
+ <phase>process-classes</phase>
+ <goals><goal>run</goal></goals>
+ <configuration>
+ <target>
+ <mkdir dir="${rust.lib.dir}/linux-x86_64-gnu"/>
+ <mkdir dir="${rust.lib.dir}/linux-aarch64-gnu"/>
+ <copy
file="${rust.target.dir}/${rust.zig.target.amd64.gnu}/${rust.release.lib.relative.path}"
+
tofile="${rust.lib.dir}/linux-x86_64-gnu/libnative-io.so"
+ failonerror="true" verbose="true"/>
+ <copy
file="${rust.target.dir}/${rust.zig.target.arm64.gnu}/${rust.release.lib.relative.path}"
+
tofile="${rust.lib.dir}/linux-aarch64-gnu/libnative-io.so"
+ failonerror="true" verbose="true"/>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
- <id>Windows</id>
+ <!--
+ Opt-in local build that uses plain cargo for the current Linux host.
+ Enable with: mvn -pl native-io -Dnative.io.pure.rust=true test
+ -->
+ <id>pure-rust-build</id>
<activation>
- <os>
- <family>Windows</family>
- </os>
+ <property>
+ <name>native.io.pure.rust</name>
+ <value>true</value>
+ </property>
</activation>
<build>
<plugins>
<plugin>
- <groupId>com.github.maven-nar</groupId>
- <artifactId>nar-maven-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <runtime>${nar.runtime}</runtime>
- <output>native-io</output>
- <libraries>
- <library>
- <type>jni</type>
-
<narSystemPackage>org.apache.bookkeeper.util.nativeio</narSystemPackage>
- </library>
- </libraries>
- <cpp>
- <optionSet>${nar.cpp.optionSet}</optionSet>
- <exceptions>false</exceptions>
- <rtti>false</rtti>
- <optimize>full</optimize>
- </cpp>
- <linker>
- <name>g++</name>
- </linker>
- </configuration>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>build-and-copy-pure-rust-lib</id>
+ <phase>process-classes</phase>
+ <goals><goal>run</goal></goals>
+ <configuration>
+ <target>
+ <fail message="Pure Rust native-io builds are supported on
Linux hosts only.">
+ <condition>
+ <not>
+ <equals arg1="${os.detected.name}" arg2="linux"/>
+ </not>
+ </condition>
+ </fail>
+ <condition property="rust.current.jar.directory"
value="linux-x86_64-gnu">
+ <or>
+ <equals arg1="${os.detected.arch}" arg2="x86_64"/>
+ <equals arg1="${os.detected.arch}" arg2="amd64"/>
+ </or>
+ </condition>
+ <condition property="rust.current.jar.directory"
value="linux-aarch64-gnu">
+ <or>
+ <equals arg1="${os.detected.arch}" arg2="aarch64"/>
+ <equals arg1="${os.detected.arch}" arg2="arm64"/>
+ <equals arg1="${os.detected.arch}" arg2="aarch_64"/>
+ </or>
+ </condition>
+ <fail unless="rust.current.jar.directory"
+ message="Pure Rust native-io builds are supported on
Linux amd64/aarch64 hosts only."/>
+ <mkdir
dir="${rust.lib.dir}/${rust.current.jar.directory}"/>
+ <exec executable="${rust.executable}" dir="${rust.dir}"
failonerror="true">
+ <arg value="build"/>
+ <arg value="--release"/>
+ </exec>
+ <copy
file="${rust.target.dir}/${rust.release.lib.relative.path}"
+
tofile="${rust.lib.dir}/${rust.current.jar.directory}/libnative-io.so"
+ failonerror="true" verbose="true"/>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
</plugins>
</build>
diff --git a/native-io/src/main/assembly/assembly.xml
b/native-io/src/main/assembly/assembly.xml
deleted file mode 100644
index 377b97ff54..0000000000
--- a/native-io/src/main/assembly/assembly.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-
--->
-<assembly>
- <id>all</id>
- <formats>
- <format>jar</format>
- </formats>
-
- <includeBaseDirectory>false</includeBaseDirectory>
- <fileSets>
- <fileSet>
-
<directory>${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-MacOSX-gpp-jni/lib/${os.arch}-MacOSX-gpp/jni
- </directory>
- <outputDirectory>lib</outputDirectory>
- <includes>
- <include>lib*</include>
- </includes>
- </fileSet>
- <fileSet>
-
<directory>${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-Linux-gpp-jni/lib/${os.arch}-Linux-gpp/jni
- </directory>
- <outputDirectory>lib</outputDirectory>
- <includes>
- <include>lib*</include>
- </includes>
- </fileSet>
- <fileSet>
-
<directory>${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-${os.name}-gpp-jni/lib/${os.arch}-${os.name}-gpp/jni
- </directory>
- <outputDirectory>lib</outputDirectory>
- <includes>
- <include>lib*</include>
- </includes>
- </fileSet>
- <fileSet>
-
<directory>${project.build.directory}/classes</directory>
- <outputDirectory></outputDirectory>
- <includes>
- <include>**/*</include>
- </includes>
- </fileSet>
- </fileSets>
-</assembly>
diff --git
a/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOJni.java
b/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOJni.java
index beaeff24ac..b4b9e63aed 100644
---
a/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOJni.java
+++
b/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOJni.java
@@ -20,6 +20,7 @@
*/
package org.apache.bookkeeper.common.util.nativeio;
+import java.util.List;
import org.apache.bookkeeper.common.util.nativelib.NativeUtils;
import org.apache.commons.lang3.SystemUtils;
@@ -50,16 +51,32 @@ class NativeIOJni {
static native int close(int fd) throws NativeIOException;
static {
- try {
- if (SystemUtils.IS_OS_MAC_OSX) {
- NativeUtils.loadLibraryFromJar("/lib/libnative-io.jnilib");
- } else if (SystemUtils.IS_OS_LINUX) {
- NativeUtils.loadLibraryFromJar("/lib/libnative-io.so");
- } else {
- throw new RuntimeException("OS not supported by Native-IO
utils");
+ String explicitPath = NativeIOLibraryPath.configuredLibraryPath();
+ if (explicitPath != null) {
+ System.load(explicitPath);
+ } else {
+ List<String> candidates =
NativeIOLibraryPath.currentPlatformLibraryCandidates();
+ if (candidates.isEmpty()) {
+ throw new IllegalStateException("No native-io JNI library
candidates found for platform "
+ + SystemUtils.OS_NAME + "/" + SystemUtils.OS_ARCH);
+ }
+
+ boolean loaded = false;
+ Throwable lastFailure = null;
+ for (String candidate : candidates) {
+ try {
+ NativeUtils.loadLibraryFromJar(candidate);
+ loaded = true;
+ break;
+ } catch (Exception | UnsatisfiedLinkError e) {
+ lastFailure = e;
+ }
+ }
+
+ if (!loaded) {
+ throw new IllegalStateException("Failed to load any native-io
JNI library candidate for platform "
+ + SystemUtils.OS_NAME + "/" + SystemUtils.OS_ARCH,
lastFailure);
}
- } catch (Exception e) {
- throw new RuntimeException(e);
}
}
}
diff --git
a/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPath.java
b/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPath.java
new file mode 100644
index 0000000000..fdc1ea2b4e
--- /dev/null
+++
b/native-io/src/main/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPath.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.bookkeeper.common.util.nativeio;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * Resolves the path of the native-io shared library inside the JAR.
+ *
+ * <p>The {@code cargo-zigbuild} Maven profile embeds two variants:
+ * <pre>
+ * lib/linux-x86_64-gnu/libnative-io.so (glibc, amd64)
+ * lib/linux-aarch64-gnu/libnative-io.so (glibc, arm64)
+ * </pre>
+ *
+ * <p>An explicit override path can be set via the system property
+ * {@code bookkeeper.native.io.library.path} or the environment variable
+ * {@code BOOKKEEPER_NATIVE_IO_LIBRARY_PATH}.
+ */
+public class NativeIOLibraryPath {
+
+ private static final String LIBRARY_PATH_ENV =
"BOOKKEEPER_NATIVE_IO_LIBRARY_PATH";
+ private static final String LIBRARY_PATH_PROPERTY =
"bookkeeper.native.io.library.path";
+
+ private NativeIOLibraryPath() {
+ }
+
+ /**
+ * Returns an explicit path from system property / env, or {@code null}.
+ */
+ public static String configuredLibraryPath() {
+ return configuredLibraryPath(
+ System.getProperty(LIBRARY_PATH_PROPERTY),
+ System.getenv(LIBRARY_PATH_ENV));
+ }
+
+ protected static String configuredLibraryPath(String propertyValue, String
envValue) {
+ return StringUtils.isNotBlank(propertyValue) ? propertyValue :
StringUtils.stripToNull(envValue);
+ }
+
+ /**
+ * Returns the ordered list of JAR-resource paths to try for the current
+ * platform and architecture. The first path that successfully loads wins.
+ */
+ public static List<String> currentPlatformLibraryCandidates() {
+ if (SystemUtils.IS_OS_LINUX) {
+ return libraryCandidates(SystemUtils.OS_NAME, SystemUtils.OS_ARCH);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ protected static List<String> libraryCandidates(String osName, String
osArch) {
+ List<String> paths = new ArrayList<>();
+ String osNameTag = osNameTag(osName);
+ String archTag = archTag(osArch);
+ paths.add("/lib/" + osNameTag + "-" + archTag +
"-gnu/libnative-io.so");
+ return paths;
+ }
+
+ private static String osNameTag(String osName) {
+ if (osName == null) {
+ throw new IllegalArgumentException("osName is null");
+ }
+
+ String normalized = osName.toLowerCase(Locale.US);
+
+ if (normalized.contains("mac") || normalized.contains("darwin")) {
+ return "macos";
+ }
+
+ if (normalized.startsWith("win")
+ || normalized.contains("mingw")
+ || normalized.contains("msys")
+ || normalized.contains("cygwin")) {
+ return "windows";
+ }
+
+ if (normalized.contains("linux")) {
+ return "linux";
+ }
+
+ throw new IllegalArgumentException("Unsupported OS: " + osName);
+ }
+
+ private static final Pattern NON_ALNUM = Pattern.compile("[^a-z0-9]");
+
+ private static String archTag(String osArch) {
+ if (osArch == null) {
+ throw new IllegalArgumentException("osArch is null");
+ }
+
+ String arch =
NON_ALNUM.matcher(osArch.toLowerCase(Locale.US)).replaceAll("");
+
+ return switch (arch) {
+ case "amd64", "x8664", "x64" -> "x86_64";
+ case "aarch64", "arm64", "armv8", "armv8l" -> "aarch64";
+ default -> throw new IllegalArgumentException("Unsupported arch: "
+ osArch);
+ };
+ }
+}
diff --git a/native-io/src/main/native-io-jni/cpp/native_io_jni.c
b/native-io/src/main/native-io-jni/cpp/native_io_jni.c
deleted file mode 100644
index 7be468518b..0000000000
--- a/native-io/src/main/native-io-jni/cpp/native_io_jni.c
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- */
-#define _GNU_SOURCE
-
-#include <jni.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#ifdef _WIN32
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-
-#include <org_apache_bookkeeper_common_util_nativeio_NativeIOJni.h>
-
-#ifdef _WIN32
-
-#define fsync(fd) fflush(fd)
-#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
-
-static ssize_t pread (int fd, void *buf, size_t count, off_t offset)
-{
- ssize_t res;
- off_t ooffset;
-
- ooffset = lseek (fd, 0, SEEK_CUR);
- lseek (fd, offset, SEEK_SET);
- res = read (fd, buf, count);
- lseek (fd, ooffset, SEEK_SET);
-
- return res;
-}
-
-static ssize_t pwrite (int fd, void *buf, size_t count, off_t offset)
-{
- ssize_t res;
- off_t ooffset;
-
- ooffset = lseek (fd, 0, SEEK_CUR);
- lseek (fd, offset, SEEK_SET);
- res = write (fd, buf, count);
- lseek (fd, ooffset, SEEK_SET);
-
- return res;
-}
-
-static int check_align(size_t align)
-{
- for (size_t i = sizeof(void *); i != 0; i *= 2)
- if (align == i)
- return 0;
- return EINVAL;
-}
-
-int posix_memalign(void **ptr, size_t align, size_t size)
-{
- if (check_align(align))
- return EINVAL;
-
- int saved_errno = errno;
- void *p = _aligned_malloc(size, align);
- if (p == NULL)
- {
- errno = saved_errno;
- return ENOMEM;
- }
-
- *ptr = p;
- return 0;
-}
-
-#endif
-
-static void throwExceptionWithErrno(JNIEnv* env, const char* message) {
- char err_msg[1024];
- strerror_r(errno, err_msg, sizeof(err_msg));
- unsigned long size = strlen(message) + strlen(err_msg) + 10;
- char* str = malloc(size);
- snprintf(str, size, "%s: %s", message, err_msg);
-
- jstring javaMessage = (*env)->NewStringUTF(env, str);
- free(str);
-
- jclass clazz = (*env)->FindClass(env,
"org/apache/bookkeeper/common/util/nativeio/NativeIOException");
- jmethodID ctorMethod = (*env)->GetMethodID(env, clazz, "<init>",
"(Ljava/lang/String;I)V");
- jobject myException = (*env)->NewObject(env, clazz, ctorMethod,
javaMessage, errno);
- (*env)->Throw(env, myException);
-}
-
-static void throwException(JNIEnv* env, const char* message) {
- (*env)->ThrowNew(env, (*env)->FindClass(env,
"org/apache/bookkeeper/common/util/nativeio/NativeIOException"), message);
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: open
- * Signature: (Ljava/lang/String;II)I
- */
-JNIEXPORT jint JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_open(
- JNIEnv *env, jclass clazz, jstring path, jint javaFlags, jint mode) {
- const char *cPath = (*env)->GetStringUTFChars(env, path, 0);
-
- int flags = 0;
- if (javaFlags & 0x01) {
- flags |= O_CREAT;
- }
-
- if (javaFlags & 0x02) {
- flags |= O_RDONLY;
- }
-
- if (javaFlags & 0x04) {
- flags |= O_WRONLY;
- }
-
- if (javaFlags & 0x08) {
- flags |= O_TRUNC;
- }
-
-#ifdef __linux__
- if (javaFlags & 0x10) {
- flags |= O_DIRECT;
- }
-#endif
-
-#ifndef _WIN32
- if (javaFlags & 0x20) {
- flags |= O_DSYNC;
- }
-#endif
-
- int fd = open(cPath, flags, mode);
-
- (*env)->ReleaseStringUTFChars(env, path, cPath);
-
- if (fd == -1) {
- throwExceptionWithErrno(env, "Failed to open file");
- }
-
- return fd;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: fsync
- * Signature: (I)I
- */
-JNIEXPORT jint JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_fsync(JNIEnv * env,
- jclass clazz,
- jint fd) {
- int res;
-
- // Guarantee compatibility for winsows.
- #ifdef _WIN32
- res = _commit((int)fd);
- #else
- res = fsync((int)fd);
- #endif
-
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to fsync");
- }
-
- return res;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: fallocate
- * Signature: (IIJJ)I
- */
-JNIEXPORT jint JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_fallocate(
- JNIEnv* env, jclass clazz,
- jint fd, jint mode, jlong offset, jlong len) {
-#ifdef __linux__
- int res = fallocate(fd, mode, offset, len);
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to fallocate");
- }
- return res;
-#else
- throwException(env, "fallocate is not available");
- return -1;
-#endif
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: posix_fadvise
- * Signature: (IJJI)I
- */
-JNIEXPORT jint JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_posix_1fadvise(
- JNIEnv* env, jclass clazz,
- jint fd, jlong offset, jlong len, jint flag) {
-#ifdef __linux__
- int res = posix_fadvise(fd, offset, len, flag);
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to posix_fadvise");
- }
- return res;
-#else
- throwException(env, "posix_fadvise is not available");
- return -1;
-#endif
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: lseek
- * Signature: (IJI)J
- */
-JNIEXPORT jlong JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_lseek(
- JNIEnv* env, jclass clazz,
- jint fd, jlong offset, jint whence) {
- int res = lseek(fd, offset, whence);
-
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to lseek");
- }
-
- return res;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: close
- * Signature: (I)I
- */
-JNIEXPORT jint JNICALL
-Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_close(JNIEnv* env,
jclass clazz,
- jint fd) {
- int res = close(fd);
-
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to close file");
- }
-
- return res;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: pwrite
- * Signature: (IJIJ)I
- */
-JNIEXPORT jint JNICALL
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_pwrite
- (JNIEnv* env, jclass clazz, jint fd, jlong pointer, jint count, jlong
offset) {
- int res = pwrite(fd, (const void*) pointer, count, offset);
-
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to write on file");
- }
-
- return res;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: posix_memalign
- * Signature: (II)J
- */
-JNIEXPORT jlong JNICALL
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_posix_1memalign
- (JNIEnv* env, jclass clazz, jint alignment, jint size) {
- void* ptr;
- int res = posix_memalign(&ptr, alignment, size);
-
- if (res != 0) {
- throwExceptionWithErrno(env, "Failed to allocate aligned memory");
- }
-
- return (jlong) ptr;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: pread
- * Signature: (IJJJ)J
- */
-JNIEXPORT jlong JNICALL
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_pread
- (JNIEnv * env, jclass clazz, jint fd, jlong pointer, jlong size, jlong
offset) {
-
- long res = pread(fd, (void*) pointer, size, offset);
-
- if (res == -1) {
- throwExceptionWithErrno(env, "Failed to read from file");
- }
-
- return res;
-}
-
-/*
- * Class: org_apache_bookkeeper_common_util_nativeio_NativeIOJni
- * Method: free
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_free
- (JNIEnv * env, jclass clazz, jlong pointer) {
- free((const void*) pointer);
-}
-
diff --git a/native-io/src/main/native-io-jni/rust/Cargo.lock
b/native-io/src/main/native-io-jni/rust/Cargo.lock
new file mode 100644
index 0000000000..83f6c34530
--- /dev/null
+++ b/native-io/src/main/native-io-jni/rust/Cargo.lock
@@ -0,0 +1,301 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "native-io"
+version = "4.18.0"
+dependencies = [
+ "jni",
+ "libc",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/native-io/src/main/native-io-jni/rust/Cargo.toml
b/native-io/src/main/native-io-jni/rust/Cargo.toml
new file mode 100644
index 0000000000..2e1322f2fe
--- /dev/null
+++ b/native-io/src/main/native-io-jni/rust/Cargo.toml
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[package]
+name = "native-io"
+version = "4.18.0"
+edition = "2021"
+license = "Apache-2.0"
+authors = ["Apache BookKeeper Community"]
+description = "Native I/O JNI library for Apache BookKeeper"
+
+[lib]
+name = "native_io"
+crate-type = ["cdylib"]
+
+[dependencies]
+jni = "0.21"
+libc = "0.2"
+
+[profile.release]
+opt-level = 3
+lto = true
+panic = "abort"
+strip = true
+codegen-units = 1
diff --git a/native-io/src/main/native-io-jni/rust/build.rs
b/native-io/src/main/native-io-jni/rust/build.rs
new file mode 100644
index 0000000000..e61e66435f
--- /dev/null
+++ b/native-io/src/main/native-io-jni/rust/build.rs
@@ -0,0 +1,37 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+//! Build script for native-io JNI library
+//! Sets the install_name for macOS dylib to match Java's expected library name
+
+fn main() {
+ let target = std::env::var("TARGET").unwrap_or_default();
+
+ // On macOS, set the install_name to libnative-io.jnilib
+ // This allows the library to be loaded with
System.loadLibrary("native-io")
+ if target.contains("darwin") {
+ println!("cargo:rustc-cdylib-link-arg=-install_name");
+ println!("cargo:rustc-cdylib-link-arg=libnative-io.jnilib");
+ }
+
+ // Rebuild if the environment changes
+ println!("cargo:rerun-if-env-changed=TARGET");
+}
diff --git a/native-io/src/main/native-io-jni/rust/src/lib.rs
b/native-io/src/main/native-io-jni/rust/src/lib.rs
new file mode 100644
index 0000000000..edca399b5a
--- /dev/null
+++ b/native-io/src/main/native-io-jni/rust/src/lib.rs
@@ -0,0 +1,346 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+//! Native I/O JNI library for Apache BookKeeper
+//!
+//! This library provides POSIX file I/O operations via JNI for
high-performance direct I/O.
+//! It is a Rust implementation of the original C native_io_jni.c
+
+use jni::objects::{JClass, JString, JThrowable};
+use jni::sys::{jint, jlong};
+use jni::JNIEnv;
+use std::ffi::{c_void, CString};
+use std::ptr;
+
+// Java flags (from NativeIO.java)
+const O_CREAT: jint = 0x01;
+const O_RDONLY: jint = 0x02;
+const O_WRONLY: jint = 0x04;
+const O_TRUNC: jint = 0x08;
+const O_DIRECT: jint = 0x10;
+const O_DSYNC: jint = 0x20;
+
+// ============================================================================
+// Helper functions for exception throwing (equivalent to C functions)
+// ============================================================================
+
+/// Throws a NativeIOException with the current errno
+fn throw_exception_with_errno(env: &mut JNIEnv, message: &str) {
+ let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
+
+ throw_exception_with_error_code(env, message, errno);
+}
+
+fn throw_exception_with_error_code(env: &mut JNIEnv, message: &str,
error_code: jint) {
+ let err_msg = std::io::Error::from_raw_os_error(error_code).to_string();
+ let full_msg = format!("{}: {}", message, err_msg);
+
+ if let Ok(class) =
+
env.find_class("org/apache/bookkeeper/common/util/nativeio/NativeIOException")
+ {
+ if let Ok(msg) = env.new_string(&full_msg) {
+ let result = env.new_object(
+ class,
+ "(Ljava/lang/String;I)V",
+ &[(&msg).into(), error_code.into()],
+ );
+ if let Ok(exception) = result {
+ let _ = env.throw(JThrowable::from(exception));
+ }
+ }
+ }
+}
+
+/// Throws a NativeIOException without errno
+fn throw_exception(env: &mut JNIEnv, message: &str) {
+ if let Ok(class) =
+
env.find_class("org/apache/bookkeeper/common/util/nativeio/NativeIOException")
+ {
+ let _ = env.throw_new(class, message);
+ }
+}
+
+// ============================================================================
+// JNI exported functions
+// ============================================================================
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: open
+/// Signature: (Ljava/lang/String;II)I
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_open(
+ mut env: JNIEnv,
+ _class: JClass,
+ path: JString,
+ java_flags: jint,
+ mode: jint,
+) -> jint {
+ // Convert Java flags to POSIX flags (same logic as C code)
+ let mut flags: i32 = 0;
+
+ if java_flags & O_CREAT != 0 {
+ flags |= libc::O_CREAT;
+ }
+ if java_flags & O_RDONLY != 0 {
+ flags |= libc::O_RDONLY;
+ }
+ if java_flags & O_WRONLY != 0 {
+ flags |= libc::O_WRONLY;
+ }
+ if java_flags & O_TRUNC != 0 {
+ flags |= libc::O_TRUNC;
+ }
+
+ // O_DIRECT is Linux only
+ #[cfg(target_os = "linux")]
+ if java_flags & O_DIRECT != 0 {
+ flags |= libc::O_DIRECT;
+ }
+
+ // O_DSYNC is not available on Windows
+ #[cfg(not(target_os = "windows"))]
+ if java_flags & O_DSYNC != 0 {
+ flags |= libc::O_DSYNC;
+ }
+
+ // Get the path string
+ let path_str: String = match env.get_string(&path) {
+ Ok(s) => s.into(),
+ Err(_) => {
+ throw_exception(&mut env, "Failed to get path string");
+ return -1;
+ }
+ };
+
+ let path_cstr = match CString::new(path_str) {
+ Ok(path) => path,
+ Err(_) => {
+ throw_exception(&mut env, "Path contains interior NUL byte");
+ return -1;
+ }
+ };
+
+ let fd = unsafe { libc::open(path_cstr.as_ptr(), flags, mode as
libc::c_uint) };
+
+ if fd == -1 {
+ throw_exception_with_errno(&mut env, "Failed to open file");
+ }
+
+ fd
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: fsync
+/// Signature: (I)I
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_fsync(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+) -> jint {
+ let res = unsafe { libc::fsync(fd) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to fsync");
+ }
+
+ res as jint
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: fallocate
+/// Signature: (IIJJ)I
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_fallocate(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+ mode: jint,
+ offset: jlong,
+ len: jlong,
+) -> jint {
+ // fallocate is Linux only
+ #[cfg(target_os = "linux")]
+ {
+ let res = unsafe { libc::fallocate(fd, mode, offset, len) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to fallocate");
+ }
+
+ res
+ }
+
+ #[cfg(not(target_os = "linux"))]
+ {
+ throw_exception(&mut env, "fallocate is not available");
+ -1
+ }
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: posix_fadvise
+/// Signature: (IJJI)I
+/// Note: In JNI, underscores in method names are escaped as _1
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_posix_1fadvise(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+ offset: jlong,
+ len: jlong,
+ flag: jint,
+) -> jint {
+ // posix_fadvise is Linux only
+ #[cfg(target_os = "linux")]
+ {
+ let res = unsafe { libc::posix_fadvise(fd, offset, len, flag) };
+
+ if res != 0 {
+ throw_exception_with_error_code(&mut env, "Failed to
posix_fadvise", res);
+ }
+
+ res
+ }
+
+ #[cfg(not(target_os = "linux"))]
+ {
+ throw_exception(&mut env, "posix_fadvise is not available");
+ -1
+ }
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: lseek
+/// Signature: (IJI)J
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_lseek(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+ offset: jlong,
+ whence: jint,
+) -> jlong {
+ let res = unsafe { libc::lseek(fd, offset, whence) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to lseek");
+ }
+
+ res as jlong
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: close
+/// Signature: (I)I
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_close(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+) -> jint {
+ let res = unsafe { libc::close(fd) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to close file");
+ }
+
+ res as jint
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: pwrite
+/// Signature: (IJIJ)I
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_pwrite(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+ pointer: jlong,
+ count: jint,
+ offset: jlong,
+) -> jint {
+ let res = unsafe { libc::pwrite(fd, pointer as *const c_void, count as
usize, offset) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to write on file");
+ }
+
+ res as jint
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: posix_memalign
+/// Signature: (II)J
+/// Note: In JNI, underscores in method names are escaped as _1
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_posix_1memalign(
+ mut env: JNIEnv,
+ _class: JClass,
+ alignment: jint,
+ size: jint,
+) -> jlong {
+ let mut ptr: *mut c_void = ptr::null_mut();
+
+ let res = unsafe { libc::posix_memalign(&mut ptr, alignment as usize, size
as usize) };
+
+ if res != 0 {
+ throw_exception_with_error_code(&mut env, "Failed to allocate aligned
memory", res);
+ return 0;
+ }
+
+ ptr as jlong
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: pread
+/// Signature: (IJJJ)J
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_pread(
+ mut env: JNIEnv,
+ _class: JClass,
+ fd: jint,
+ pointer: jlong,
+ size: jlong,
+ offset: jlong,
+) -> jlong {
+ let res = unsafe { libc::pread(fd, pointer as *mut c_void, size as usize,
offset) };
+
+ if res == -1 {
+ throw_exception_with_errno(&mut env, "Failed to read from file");
+ }
+
+ res as jlong
+}
+
+/// Class: org.apache.bookkeeper.common.util.nativeio.NativeIOJni
+/// Method: free
+/// Signature: (J)V
+#[no_mangle]
+pub extern "system" fn
Java_org_apache_bookkeeper_common_util_nativeio_NativeIOJni_free(
+ _env: JNIEnv,
+ _class: JClass,
+ pointer: jlong,
+) {
+ unsafe {
+ libc::free(pointer as *mut c_void);
+ }
+}
diff --git
a/native-io/src/test/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPathTest.java
b/native-io/src/test/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPathTest.java
new file mode 100644
index 0000000000..4c7586ae30
--- /dev/null
+++
b/native-io/src/test/java/org/apache/bookkeeper/common/util/nativeio/NativeIOLibraryPathTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bookkeeper.common.util.nativeio;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class NativeIOLibraryPathTest {
+
+ private void assertLibraryCandidates(String osName, String osArch,
String... expectedCandidates) {
+ List<String> expected = expectedCandidates.length == 0
+ ? Collections.emptyList()
+ : List.of(expectedCandidates);
+ assertEquals(expected, NativeIOLibraryPath.libraryCandidates(osName,
osArch));
+ }
+
+ @Test
+ public void testLinuxAmd64() {
+ assertLibraryCandidates("Linux", "amd64",
"/lib/linux-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testLinuxX8664() {
+ assertLibraryCandidates("Linux", "x86_64",
"/lib/linux-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testLinuxAarch64() {
+ assertLibraryCandidates("Linux", "aarch64",
"/lib/linux-aarch64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testLinuxUnknownArch() {
+ assertThrows(IllegalArgumentException.class, () ->
assertLibraryCandidates("Linux", "riscv64"));
+ }
+
+ @Test
+ public void testMacOsAmd64() {
+ assertLibraryCandidates("MacOS", "amd64",
"/lib/macos-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testMacAmd64() {
+ assertLibraryCandidates("Mac", "amd64",
"/lib/macos-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testMacOsWithSpaceAmd64() {
+ assertLibraryCandidates("Mac OS", "amd64",
"/lib/macos-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testWindowsAmd64() {
+ assertLibraryCandidates("Windows", "amd64",
"/lib/windows-x86_64-gnu/libnative-io.so");
+ }
+
+ @Test
+ public void testExplicitPathProperty() {
+ assertEquals("/tmp/libnative-io.so",
+
NativeIOLibraryPath.configuredLibraryPath("/tmp/libnative-io.so", null));
+ }
+
+ @Test
+ public void testExplicitPathEnv() {
+ assertEquals("/tmp/from-env.so",
+ NativeIOLibraryPath.configuredLibraryPath(null,
"/tmp/from-env.so"));
+ }
+
+ @Test
+ public void testPropertyTakesPrecedenceOverEnv() {
+ assertEquals("/tmp/prop.so",
+ NativeIOLibraryPath.configuredLibraryPath("/tmp/prop.so",
"/tmp/env.so"));
+ }
+
+ @Test
+ public void testBlankValuesReturnNull() {
+ assertNull(NativeIOLibraryPath.configuredLibraryPath(" ", ""));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 8815ed0e76..a3ba5c7f3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1234,6 +1234,9 @@
<!-- test resources -->
<exclude>**/test_conf_2.conf</exclude>
+
+ <!-- zig -->
+ <exclude>**/.zig-cache/**</exclude>
</excludes>
<consoleOutput>true</consoleOutput>
</configuration>