diff --git a/.github/workflows/bk-ci.yml b/.github/workflows/bk-ci.yml index 64116a3fe61..ce47fa122ec 100644 --- a/.github/workflows/bk-ci.yml +++ b/.github/workflows/bk-ci.yml @@ -82,6 +82,22 @@ jobs: distribution: 'temurin' java-version: 17 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + # Cross-compilation targets: Linux amd64 and arm64 (glibc) + targets: >- + x86_64-unknown-linux-gnu, + aarch64-unknown-linux-gnu + + - name: Set up Zig + if: steps.check_changes.outputs.docs_only != 'true' + uses: mlugg/setup-zig@v2 + + - name: Install cargo-zigbuild + if: steps.check_changes.outputs.docs_only != 'true' + run: cargo install --locked cargo-zigbuild + - name: Validate pull request if: steps.check_changes.outputs.docs_only != 'true' run: | @@ -161,6 +177,20 @@ jobs: distribution: 'temurin' java-version: 17 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - name: Tune Java DNS TTL settings run: | sudo tee -a $JAVA_HOME/conf/security/java.security <- + x86_64-unknown-linux-gnu, + aarch64-unknown-linux-gnu + + - name: Set up Zig + uses: mlugg/setup-zig@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - name: Tune Java DNS TTL settings run: | sudo tee -a $JAVA_HOME/conf/security/java.security <- + x86_64-unknown-linux-gnu, + aarch64-unknown-linux-gnu + + - name: Set up Zig + uses: mlugg/setup-zig@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - name: Restore released versions cache uses: actions/cache/restore@v4 with: @@ -437,6 +495,20 @@ jobs: distribution: 'temurin' java-version: 17 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - name: mvn package run: mvn -B -nsu clean package -DskipTests @@ -471,6 +543,20 @@ jobs: distribution: 'temurin' java-version: 17 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - name: mvn package run: mvn -B -nsu clean package -DskipTests @@ -514,6 +600,20 @@ jobs: distribution: 'temurin' java-version: ${{ matrix.jdk_version }} + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild + - 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 097b18a9b44..0fc068e79a4 100644 --- a/.github/workflows/bk-streamstorage-python.yml +++ b/.github/workflows/bk-streamstorage-python.yml @@ -73,6 +73,17 @@ jobs: with: distribution: 'temurin' java-version: 17 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild - 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 @@ -84,4 +95,3 @@ jobs: - name: Test run: ./stream/clients/python/scripts/docker_integration_tests.sh - diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 156468c5f31..05ccb4272c6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -85,6 +85,23 @@ jobs: distribution: 'temurin' java-version: 17 + - name: Set up Rust + if: steps.check_changes.outputs.docs_only != 'true' + uses: dtolnay/rust-toolchain@stable + with: + # Cross-compilation targets: Linux amd64 and arm64 (glibc) + targets: >- + x86_64-unknown-linux-gnu, + aarch64-unknown-linux-gnu + + - name: Set up Zig + if: steps.check_changes.outputs.docs_only != 'true' + uses: mlugg/setup-zig@v2 + + - name: Install cargo-zigbuild + if: steps.check_changes.outputs.docs_only != 'true' + run: cargo install --locked cargo-zigbuild + - 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 331ad9b61cd..101111bdbb2 100644 --- a/.github/workflows/java21-daily-build.yml +++ b/.github/workflows/java21-daily-build.yml @@ -42,6 +42,17 @@ jobs: with: distribution: 'temurin' java-version: 21 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild - 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 513c28241b7..8d6dc521f38 100644 --- a/.github/workflows/windows-daily-build.yml +++ b/.github/workflows/windows-daily-build.yml @@ -43,6 +43,17 @@ jobs: with: distribution: 'temurin' java-version: 21 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + 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@v2 + - name: Install cargo-zigbuild + run: cargo install --locked cargo-zigbuild - 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 923ea43dbeb..fe3d15820ba 100644 --- a/native-io/pom.xml +++ b/native-io/pom.xml @@ -24,13 +24,19 @@ native-io - nar + jar Apache BookKeeper :: Native IO Library Native IO Library - dynamic - -msse4.2 -mpclmul + ${project.basedir}/src/main/native-io-jni/rust + ${rust.dir}/target + cargo + release/libnative_io.so + x86_64-unknown-linux-gnu + aarch64-unknown-linux-gnu + ${project.build.directory}/classes/lib + false @@ -43,6 +49,12 @@ org.apache.commons commons-lang3 + + junit + junit + ${junit.version} + test + @@ -51,175 +63,156 @@ org.apache.maven.plugins maven-compiler-plugin - - com.github.maven-nar - nar-maven-plugin - true - - - org.apache.maven.plugins - maven-assembly-plugin - - - src/main/assembly/assembly.xml - - false - posix - - - - make-assembly - package - - single - - - - org.apache.rat apache-rat-plugin + + + **/rust/target/** + + - - jdk-without-javah + cargo-zigbuild - [10,) + + native.io.pure.rust + !true + - com.github.maven-nar - nar-maven-plugin - true + org.codehaus.mojo + exec-maven-plugin - - - default-nar-javah - none - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - -h - ${project.build.directory}/nar/javah-include - - - - - - - - mac - - - Mac OS X - - - - - - com.github.maven-nar - nar-maven-plugin - true - - ${nar.runtime} - native-io - - - jni - org.apache.bookkeeper.util.nativeio - - - - ${nar.cpp.optionSet} - false - false - full - - + + cargo-zigbuild-amd64-gnu + compile + exec + + ${rust.executable} + ${rust.dir} + + zigbuild + --release + --target + ${rust.zig.target.amd64.gnu} + + + + + cargo-zigbuild-arm64-gnu + compile + exec + + ${rust.executable} + ${rust.dir} + + zigbuild + --release + --target + ${rust.zig.target.arm64.gnu} + + + + - - - - - - Linux - - - Linux - - - - - com.github.maven-nar - nar-maven-plugin - true - - ${nar.runtime} - native-io - - - jni - org.apache.bookkeeper.util.nativeio - - - - ${nar.cpp.optionSet} - false - false - full - - + maven-antrun-plugin + + + copy-zig-libs + process-classes + run + + + + + + + + + + - Windows + + pure-rust-build - - Windows - + + native.io.pure.rust + true + - com.github.maven-nar - nar-maven-plugin - true - - ${nar.runtime} - native-io - - - jni - org.apache.bookkeeper.util.nativeio - - - - ${nar.cpp.optionSet} - false - false - full - - - g++ - - + maven-antrun-plugin + + + build-and-copy-pure-rust-lib + process-classes + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/native-io/src/main/assembly/assembly.xml b/native-io/src/main/assembly/assembly.xml deleted file mode 100644 index 377b97ff54e..00000000000 --- a/native-io/src/main/assembly/assembly.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - all - - jar - - - false - - - ${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-MacOSX-gpp-jni/lib/${os.arch}-MacOSX-gpp/jni - - lib - - lib* - - - - ${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-Linux-gpp-jni/lib/${os.arch}-Linux-gpp/jni - - lib - - lib* - - - - ${project.build.directory}/nar/${project.artifactId}-${project.version}-${os.arch}-${os.name}-gpp-jni/lib/${os.arch}-${os.name}-gpp/jni - - lib - - lib* - - - - ${project.build.directory}/classes - - - **/* - - - - 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 beaeff24ac4..b4b9e63aed3 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 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 00000000000..fdc1ea2b4e6 --- /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. + * + *

The {@code cargo-zigbuild} Maven profile embeds two variants: + *

+ *   lib/linux-x86_64-gnu/libnative-io.so   (glibc, amd64)
+ *   lib/linux-aarch64-gnu/libnative-io.so  (glibc, arm64)
+ * 
+ * + *

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 currentPlatformLibraryCandidates() { + if (SystemUtils.IS_OS_LINUX) { + return libraryCandidates(SystemUtils.OS_NAME, SystemUtils.OS_ARCH); + } else { + return Collections.emptyList(); + } + } + + protected static List libraryCandidates(String osName, String osArch) { + List 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 7be468518b0..00000000000 --- 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 -#include -#include -#include -#include -#ifdef _WIN32 -#include -#else -#include -#endif - -#include - -#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, "", "(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 00000000000..83f6c345306 --- /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 00000000000..2e1322f2fe0 --- /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 00000000000..e61e66435f5 --- /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 00000000000..edca399b5a6 --- /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 00000000000..4c7586ae306 --- /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 + *

+ * 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 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 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 8815ed0e768..a3ba5c7f3d1 100644 --- a/pom.xml +++ b/pom.xml @@ -1234,6 +1234,9 @@ **/test_conf_2.conf + + + **/.zig-cache/** true