diff options
author | 2024-07-09 23:24:06 +0000 | |
---|---|---|
committer | 2024-07-19 09:07:27 -0700 | |
commit | 7692bcb3cf7c235308e36468be8ed8dc37018843 (patch) | |
tree | 49b3fb11f3d3f25e1ac90e76cd50a63e208edf0b | |
parent | 06a2deec0edce750414968fdc3ed886bea8b666a (diff) |
[Ravenwood] Make Os.stat() family available
Add Ravenwood support for stat, lstat, and fstat.
Bug: 350811257
Test: atest RavenwoodRuntimeTest
Flag: TEST_ONLY (ravenwood)
Merged-in: I7c13021f539a38b3eb493ddbf3c04eadd5d8ef81
Change-Id: I7c13021f539a38b3eb493ddbf3c04eadd5d8ef81
-rw-r--r-- | ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java | 13 | ||||
-rw-r--r-- | ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java | 121 | ||||
-rw-r--r-- | ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java | 79 | ||||
-rw-r--r-- | ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java (renamed from ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java) | 31 | ||||
-rw-r--r-- | ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java | 85 | ||||
-rw-r--r-- | ravenwood/runtime-jni/ravenwood_runtime.cpp | 96 | ||||
-rw-r--r-- | ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java | 123 |
7 files changed, 529 insertions, 19 deletions
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java index e031eb27513b..8a1fe62db4e1 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java @@ -30,7 +30,6 @@ public final class Os { return RavenwoodRuntimeNative.lseek(fd, offset, whence); } - public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return RavenwoodRuntimeNative.pipe2(flags); } @@ -42,4 +41,16 @@ public final class Os { public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return RavenwoodRuntimeNative.fcntlInt(fd, cmd, arg); } + + public static StructStat fstat(FileDescriptor fd) throws ErrnoException { + return RavenwoodRuntimeNative.fstat(fd); + } + + public static StructStat lstat(String path) throws ErrnoException { + return RavenwoodRuntimeNative.lstat(path); + } + + public static StructStat stat(String path) throws ErrnoException { + return RavenwoodRuntimeNative.stat(path); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java new file mode 100644 index 000000000000..a8b1fca464dd --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed 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 android.system; + +import libcore.util.Objects; + +/** + * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}. + * Corresponds to C's {@code struct stat} from {@code <stat.h>}. + */ +public final class StructStat { + /** Device ID of device containing file. */ + public final long st_dev; /*dev_t*/ + + /** File serial number (inode). */ + public final long st_ino; /*ino_t*/ + + /** Mode (permissions) of file. */ + public final int st_mode; /*mode_t*/ + + /** Number of hard links to the file. */ + public final long st_nlink; /*nlink_t*/ + + /** User ID of file. */ + public final int st_uid; /*uid_t*/ + + /** Group ID of file. */ + public final int st_gid; /*gid_t*/ + + /** Device ID (if file is character or block special). */ + public final long st_rdev; /*dev_t*/ + + /** + * For regular files, the file size in bytes. + * For symbolic links, the length in bytes of the pathname contained in the symbolic link. + * For a shared memory object, the length in bytes. + * For a typed memory object, the length in bytes. + * For other file types, the use of this field is unspecified. + */ + public final long st_size; /*off_t*/ + + /** Seconds part of time of last access. */ + public final long st_atime; /*time_t*/ + + /** StructTimespec with time of last access. */ + public final StructTimespec st_atim; + + /** Seconds part of time of last data modification. */ + public final long st_mtime; /*time_t*/ + + /** StructTimespec with time of last modification. */ + public final StructTimespec st_mtim; + + /** Seconds part of time of last status change */ + public final long st_ctime; /*time_t*/ + + /** StructTimespec with time of last status change. */ + public final StructTimespec st_ctim; + + /** + * A file system-specific preferred I/O block size for this object. + * For some file system types, this may vary from file to file. + */ + public final long st_blksize; /*blksize_t*/ + + /** Number of blocks allocated for this object. */ + public final long st_blocks; /*blkcnt_t*/ + + /** + * Constructs an instance with the given field values. + */ + public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid, + long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime, + long st_blksize, long st_blocks) { + this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid, + st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L), + new StructTimespec(st_ctime, 0L), st_blksize, st_blocks); + } + + /** + * Constructs an instance with the given field values. + */ + public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid, + long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim, + StructTimespec st_ctim, long st_blksize, long st_blocks) { + this.st_dev = st_dev; + this.st_ino = st_ino; + this.st_mode = st_mode; + this.st_nlink = st_nlink; + this.st_uid = st_uid; + this.st_gid = st_gid; + this.st_rdev = st_rdev; + this.st_size = st_size; + this.st_atime = st_atim.tv_sec; + this.st_mtime = st_mtim.tv_sec; + this.st_ctime = st_ctim.tv_sec; + this.st_atim = st_atim; + this.st_mtim = st_mtim; + this.st_ctim = st_ctim; + this.st_blksize = st_blksize; + this.st_blocks = st_blocks; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java new file mode 100644 index 000000000000..c10678057eeb --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed 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 android.system; + +import libcore.util.Objects;; + +/** + * Corresponds to C's {@code struct timespec} from {@code <time.h>}. + */ +public final class StructTimespec implements Comparable<StructTimespec> { + /** Seconds part of time of last data modification. */ + public final long tv_sec; /*time_t*/ + + /** Nanoseconds (values are [0, 999999999]). */ + public final long tv_nsec; + + public StructTimespec(long tv_sec, long tv_nsec) { + this.tv_sec = tv_sec; + this.tv_nsec = tv_nsec; + if (tv_nsec < 0 || tv_nsec > 999_999_999) { + throw new IllegalArgumentException( + "tv_nsec value " + tv_nsec + " is not in [0, 999999999]"); + } + } + + @Override + public int compareTo(StructTimespec other) { + if (tv_sec > other.tv_sec) { + return 1; + } + if (tv_sec < other.tv_sec) { + return -1; + } + if (tv_nsec > other.tv_nsec) { + return 1; + } + if (tv_nsec < other.tv_nsec) { + return -1; + } + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StructTimespec that = (StructTimespec) o; + + if (tv_sec != that.tv_sec) return false; + return tv_nsec == that.tv_nsec; + } + + @Override + public int hashCode() { + int result = (int) (tv_sec ^ (tv_sec >>> 32)); + result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32)); + return result; + } + + @Override + public String toString() { + return Objects.toString(this); + } +} diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java index 65402219ebee..e9b305e5d789 100644 --- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java @@ -15,6 +15,9 @@ */ package com.android.ravenwood.common; +import android.system.ErrnoException; +import android.system.StructStat; + import java.io.FileDescriptor; /** @@ -31,19 +34,25 @@ public class RavenwoodRuntimeNative { public static native void applyFreeFunction(long freeFunction, long nativePtr); - public static native long nLseek(int fd, long offset, int whence); + private static native long nLseek(int fd, long offset, int whence) throws ErrnoException; + + private static native int[] nPipe2(int flags) throws ErrnoException; + + private static native int nDup(int oldfd) throws ErrnoException; + + private static native int nFcntlInt(int fd, int cmd, int arg) throws ErrnoException; - public static native int[] nPipe2(int flags); + private static native StructStat nFstat(int fd) throws ErrnoException; - public static native int nDup(int oldfd); + public static native StructStat lstat(String path) throws ErrnoException; - public static native int nFcntlInt(int fd, int cmd, int arg); + public static native StructStat stat(String path) throws ErrnoException; - public static long lseek(FileDescriptor fd, long offset, int whence) { + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence); } - public static FileDescriptor[] pipe2(int flags) { + public static FileDescriptor[] pipe2(int flags) throws ErrnoException { var fds = nPipe2(flags); var ret = new FileDescriptor[] { new FileDescriptor(), @@ -55,7 +64,7 @@ public class RavenwoodRuntimeNative { return ret; } - public static FileDescriptor dup(FileDescriptor fd) { + public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException { var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd)); var retFd = new java.io.FileDescriptor(); @@ -63,9 +72,15 @@ public class RavenwoodRuntimeNative { return retFd; } - public static int fcntlInt(FileDescriptor fd, int cmd, int arg) { + public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { var fdInt = JvmWorkaround.getInstance().getFdInt(fd); return nFcntlInt(fdInt, cmd, arg); } + + public static StructStat fstat(FileDescriptor fd) throws ErrnoException { + var fdInt = JvmWorkaround.getInstance().getFdInt(fd); + + return nFstat(fdInt); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java new file mode 100644 index 000000000000..3781fcf4e891 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed 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 libcore.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +public final class Objects { + private Objects() {} + + /** + * Returns a string reporting the value of each declared field, via reflection. + * Static and transient fields are automatically skipped. Produces output like + * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]". + */ + public static String toString(Object o) { + Class<?> c = o.getClass(); + StringBuilder sb = new StringBuilder(); + sb.append(c.getSimpleName()).append('['); + int i = 0; + for (Field f : c.getDeclaredFields()) { + if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) { + continue; + } + f.setAccessible(true); + try { + Object value = f.get(o); + + if (i++ > 0) { + sb.append(','); + } + + sb.append(f.getName()); + sb.append('='); + + if (value.getClass().isArray()) { + if (value.getClass() == boolean[].class) { + sb.append(Arrays.toString((boolean[]) value)); + } else if (value.getClass() == byte[].class) { + sb.append(Arrays.toString((byte[]) value)); + } else if (value.getClass() == char[].class) { + sb.append(Arrays.toString((char[]) value)); + } else if (value.getClass() == double[].class) { + sb.append(Arrays.toString((double[]) value)); + } else if (value.getClass() == float[].class) { + sb.append(Arrays.toString((float[]) value)); + } else if (value.getClass() == int[].class) { + sb.append(Arrays.toString((int[]) value)); + } else if (value.getClass() == long[].class) { + sb.append(Arrays.toString((long[]) value)); + } else if (value.getClass() == short[].class) { + sb.append(Arrays.toString((short[]) value)); + } else { + sb.append(Arrays.toString((Object[]) value)); + } + } else if (value.getClass() == Character.class) { + sb.append('\'').append(value).append('\''); + } else if (value.getClass() == String.class) { + sb.append('"').append(value).append('"'); + } else { + sb.append(value); + } + } catch (IllegalAccessException unexpected) { + throw new AssertionError(unexpected); + } + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index 34cf9f915677..e0a3e1c9edf6 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -19,6 +19,9 @@ #include <string.h> #include <unistd.h> #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedUtfChars.h> + #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" @@ -41,6 +44,75 @@ static rc_t throwIfMinusOne(JNIEnv* env, const char* name, rc_t rc) { return rc; } +// ---- Helper functions --- + +static jclass g_StructStat; +static jclass g_StructTimespecClass; + +static jclass findClass(JNIEnv* env, const char* name) { + ScopedLocalRef<jclass> localClass(env, env->FindClass(name)); + jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get())); + if (result == NULL) { + ALOGE("failed to find class '%s'", name); + abort(); + } + return result; +} + +static jobject makeStructTimespec(JNIEnv* env, const struct timespec& ts) { + static jmethodID ctor = env->GetMethodID(g_StructTimespecClass, "<init>", + "(JJ)V"); + if (ctor == NULL) { + return NULL; + } + return env->NewObject(g_StructTimespecClass, ctor, + static_cast<jlong>(ts.tv_sec), static_cast<jlong>(ts.tv_nsec)); +} + +static jobject makeStructStat(JNIEnv* env, const struct stat64& sb) { + static jmethodID ctor = env->GetMethodID(g_StructStat, "<init>", + "(JJIJIIJJLandroid/system/StructTimespec;Landroid/system/StructTimespec;Landroid/system/StructTimespec;JJ)V"); + if (ctor == NULL) { + return NULL; + } + + jobject atim_timespec = makeStructTimespec(env, sb.st_atim); + if (atim_timespec == NULL) { + return NULL; + } + jobject mtim_timespec = makeStructTimespec(env, sb.st_mtim); + if (mtim_timespec == NULL) { + return NULL; + } + jobject ctim_timespec = makeStructTimespec(env, sb.st_ctim); + if (ctim_timespec == NULL) { + return NULL; + } + + return env->NewObject(g_StructStat, ctor, + static_cast<jlong>(sb.st_dev), static_cast<jlong>(sb.st_ino), + static_cast<jint>(sb.st_mode), static_cast<jlong>(sb.st_nlink), + static_cast<jint>(sb.st_uid), static_cast<jint>(sb.st_gid), + static_cast<jlong>(sb.st_rdev), static_cast<jlong>(sb.st_size), + atim_timespec, mtim_timespec, ctim_timespec, + static_cast<jlong>(sb.st_blksize), static_cast<jlong>(sb.st_blocks)); +} + +static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) { + ScopedUtfChars path(env, javaPath); + if (path.c_str() == NULL) { + return NULL; + } + struct stat64 sb; + int rc = isLstat ? TEMP_FAILURE_RETRY(lstat64(path.c_str(), &sb)) + : TEMP_FAILURE_RETRY(stat64(path.c_str(), &sb)); + if (rc == -1) { + throwErrnoException(env, isLstat ? "lstat" : "stat"); + return NULL; + } + return makeStructStat(env, sb); +} + // ---- JNI methods ---- typedef void (*FreeFunction)(void*); @@ -77,6 +149,24 @@ static jlong nDup(JNIEnv* env, jclass, jint fd) { return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, F_DUPFD_CLOEXEC, 0))); } +static jobject nFstat(JNIEnv* env, jobject, jint fd) { + struct stat64 sb; + int rc = TEMP_FAILURE_RETRY(fstat64(fd, &sb)); + if (rc == -1) { + throwErrnoException(env, "fstat"); + return NULL; + } + return makeStructStat(env, sb); +} + +static jobject Linux_lstat(JNIEnv* env, jobject, jstring javaPath) { + return doStat(env, javaPath, true); +} + +static jobject Linux_stat(JNIEnv* env, jobject, jstring javaPath) { + return doStat(env, javaPath, false); +} + // ---- Registration ---- static const JNINativeMethod sMethods[] = @@ -86,6 +176,9 @@ static const JNINativeMethod sMethods[] = { "nLseek", "(IJI)J", (void*)nLseek }, { "nPipe2", "(I)[I", (void*)nPipe2 }, { "nDup", "(I)I", (void*)nDup }, + { "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat }, + { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat }, + { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) @@ -101,6 +194,9 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) ALOGI("%s: JNI_OnLoad", __FILE__); + g_StructStat = findClass(env, "android/system/StructStat"); + g_StructTimespecClass = findClass(env, "android/system/StructTimespec"); + jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative", sMethods, NELEM(sMethods)); if (res < 0) { diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java index b5038e68516d..05275b29e48b 100644 --- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java +++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java @@ -15,15 +15,26 @@ */ package com.android.ravenwood.runtimetest; +import static android.system.OsConstants.S_ISBLK; +import static android.system.OsConstants.S_ISCHR; +import static android.system.OsConstants.S_ISDIR; +import static android.system.OsConstants.S_ISFIFO; +import static android.system.OsConstants.S_ISLNK; +import static android.system.OsConstants.S_ISREG; +import static android.system.OsConstants.S_ISSOCK; + import static org.junit.Assert.assertEquals; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; + import android.system.Os; import android.system.OsConstants; +import android.system.StructStat; +import android.system.StructTimespec; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.ravenwood.common.JvmWorkaround; -import com.android.ravenwood.common.RavenwoodRuntimeNative; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,8 +43,15 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class OsTest { @@ -41,7 +59,7 @@ public class OsTest { void accept(T var1) throws Exception; } - private void withTestFile(ConsumerWithThrow<FileDescriptor> consumer) throws Exception { + private void withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer) throws Exception { File file = File.createTempFile("osTest", "bin"); try (var raf = new RandomAccessFile(file, "rw")) { var fd = raf.getFD(); @@ -57,9 +75,20 @@ public class OsTest { } } + private void withTestFile(ConsumerWithThrow<Path> consumer) throws Exception { + var path = Files.createTempFile("osTest", "bin"); + try (var os = Files.newOutputStream(path)) { + os.write(1); + os.write(2); + os.write(3); + os.write(4); + } + consumer.accept(path); + } + @Test public void testLseek() throws Exception { - withTestFile((fd) -> { + withTestFileFD((fd) -> { assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET)); assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR)); assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR)); @@ -68,7 +97,7 @@ public class OsTest { @Test public void testDup() throws Exception { - withTestFile((fd) -> { + withTestFileFD((fd) -> { var dup = Os.dup(fd); checkAreDup(fd, dup); @@ -85,7 +114,7 @@ public class OsTest { @Test public void testFcntlInt() throws Exception { - withTestFile((fd) -> { + withTestFileFD((fd) -> { var dupInt = Os.fcntlInt(fd, 0, 0); var dup = new FileDescriptor(); @@ -95,16 +124,90 @@ public class OsTest { }); } - private static void write(FileDescriptor fd, int oneByte) throws IOException { + @Test + public void testStat() throws Exception { + withTestFile(path -> { + var attr = Files.readAttributes(path, PosixFileAttributes.class); + var stat = Os.stat(path.toAbsolutePath().toString()); + assertAttributesEqual(attr, stat); + }); + } + + @Test + public void testLstat() throws Exception { + withTestFile(path -> { + // Create a symbolic link + var lnk = Files.createTempFile("osTest", "lnk"); + Files.delete(lnk); + Files.createSymbolicLink(lnk, path); + + // Test lstat + var attr = Files.readAttributes(lnk, PosixFileAttributes.class, NOFOLLOW_LINKS); + var stat = Os.lstat(lnk.toAbsolutePath().toString()); + assertAttributesEqual(attr, stat); + + // Test stat + var followAttr = Files.readAttributes(lnk, PosixFileAttributes.class); + var followStat = Os.stat(lnk.toAbsolutePath().toString()); + assertAttributesEqual(followAttr, followStat); + }); + } + + @Test + public void testFstat() throws Exception { + withTestFile(path -> { + var attr = Files.readAttributes(path, PosixFileAttributes.class); + try (var raf = new RandomAccessFile(path.toFile(), "r")) { + var fd = raf.getFD(); + var stat = Os.fstat(fd); + assertAttributesEqual(attr, stat); + } + }); + } + + // Verify StructStat values from libcore against native JVM PosixFileAttributes + private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) { + assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim)); + assertEquals(attr.size(), stat.st_size); + assertEquals(attr.isDirectory(), S_ISDIR(stat.st_mode)); + assertEquals(attr.isRegularFile(), S_ISREG(stat.st_mode)); + assertEquals(attr.isSymbolicLink(), S_ISLNK(stat.st_mode)); + assertEquals(attr.isOther(), S_ISCHR(stat.st_mode) + || S_ISBLK(stat.st_mode) || S_ISFIFO(stat.st_mode) || S_ISSOCK(stat.st_mode)); + assertEquals(attr.permissions(), convertModeToPosixPerms(stat.st_mode)); + + } + + private static FileTime convertTimespecToFileTime(StructTimespec ts) { + var nanos = TimeUnit.SECONDS.toNanos(ts.tv_sec); + nanos += ts.tv_nsec; + return FileTime.from(nanos, TimeUnit.NANOSECONDS); + } + + private static Set<PosixFilePermission> convertModeToPosixPerms(int mode) { + var set = new HashSet<PosixFilePermission>(); + if ((mode & OsConstants.S_IRUSR) != 0) set.add(PosixFilePermission.OWNER_READ); + if ((mode & OsConstants.S_IWUSR) != 0) set.add(PosixFilePermission.OWNER_WRITE); + if ((mode & OsConstants.S_IXUSR) != 0) set.add(PosixFilePermission.OWNER_EXECUTE); + if ((mode & OsConstants.S_IRGRP) != 0) set.add(PosixFilePermission.GROUP_READ); + if ((mode & OsConstants.S_IWGRP) != 0) set.add(PosixFilePermission.GROUP_WRITE); + if ((mode & OsConstants.S_IXGRP) != 0) set.add(PosixFilePermission.GROUP_EXECUTE); + if ((mode & OsConstants.S_IROTH) != 0) set.add(PosixFilePermission.OTHERS_READ); + if ((mode & OsConstants.S_IWOTH) != 0) set.add(PosixFilePermission.OTHERS_WRITE); + if ((mode & OsConstants.S_IXOTH) != 0) set.add(PosixFilePermission.OTHERS_EXECUTE); + return set; + } + + private static void write(FileDescriptor fd, int oneByte) throws Exception { // Create a dup to avoid closing the FD. - try (var dup = new FileOutputStream(RavenwoodRuntimeNative.dup(fd))) { + try (var dup = new FileOutputStream(Os.dup(fd))) { dup.write(oneByte); } } - private static int read(FileDescriptor fd) throws IOException { + private static int read(FileDescriptor fd) throws Exception { // Create a dup to avoid closing the FD. - try (var dup = new FileInputStream(RavenwoodRuntimeNative.dup(fd))) { + try (var dup = new FileInputStream(Os.dup(fd))) { return dup.read(); } } |