1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.ravenwoodtest.runtimetest; 17 18 import static android.system.OsConstants.S_ISBLK; 19 import static android.system.OsConstants.S_ISCHR; 20 import static android.system.OsConstants.S_ISDIR; 21 import static android.system.OsConstants.S_ISFIFO; 22 import static android.system.OsConstants.S_ISLNK; 23 import static android.system.OsConstants.S_ISREG; 24 import static android.system.OsConstants.S_ISSOCK; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertNotEquals; 28 import static org.junit.Assert.assertTrue; 29 30 import static java.nio.file.LinkOption.NOFOLLOW_LINKS; 31 32 import android.system.Os; 33 import android.system.OsConstants; 34 import android.system.StructStat; 35 import android.system.StructTimespec; 36 37 import androidx.test.ext.junit.runners.AndroidJUnit4; 38 39 import com.android.ravenwood.common.JvmWorkaround; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.io.File; 45 import java.io.FileDescriptor; 46 import java.io.FileInputStream; 47 import java.io.FileOutputStream; 48 import java.io.RandomAccessFile; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.nio.file.attribute.FileTime; 52 import java.nio.file.attribute.PosixFileAttributes; 53 import java.nio.file.attribute.PosixFilePermission; 54 import java.util.HashSet; 55 import java.util.Set; 56 import java.util.concurrent.CountDownLatch; 57 import java.util.concurrent.TimeUnit; 58 59 @RunWith(AndroidJUnit4.class) 60 public class OsTest { 61 62 public interface ConsumerWithThrow<T> { accept(T var1)63 void accept(T var1) throws Exception; 64 } 65 withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer)66 private void withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer) throws Exception { 67 File file = File.createTempFile("osTest", "bin"); 68 try (var raf = new RandomAccessFile(file, "rw")) { 69 var fd = raf.getFD(); 70 71 try (var os = new FileOutputStream(fd)) { 72 os.write(1); 73 os.write(2); 74 os.write(3); 75 os.write(4); 76 77 consumer.accept(fd); 78 } 79 } 80 } 81 withTestFile(ConsumerWithThrow<Path> consumer)82 private void withTestFile(ConsumerWithThrow<Path> consumer) throws Exception { 83 var path = Files.createTempFile("osTest", "bin"); 84 try (var os = Files.newOutputStream(path)) { 85 os.write(1); 86 os.write(2); 87 os.write(3); 88 os.write(4); 89 } 90 consumer.accept(path); 91 } 92 93 @Test testLseek()94 public void testLseek() throws Exception { 95 withTestFileFD((fd) -> { 96 assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET)); 97 assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR)); 98 assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR)); 99 }); 100 } 101 102 @Test testDup()103 public void testDup() throws Exception { 104 withTestFileFD((fd) -> { 105 var dup = Os.dup(fd); 106 107 checkAreDup(fd, dup); 108 }); 109 } 110 111 @Test testPipe2()112 public void testPipe2() throws Exception { 113 var fds = Os.pipe2(0); 114 115 write(fds[1], 123); 116 assertEquals(123, read(fds[0])); 117 } 118 119 @Test testFcntlInt()120 public void testFcntlInt() throws Exception { 121 withTestFileFD((fd) -> { 122 var dupInt = Os.fcntlInt(fd, 0, 0); 123 124 var dup = new FileDescriptor(); 125 JvmWorkaround.getInstance().setFdInt(dup, dupInt); 126 127 checkAreDup(fd, dup); 128 }); 129 } 130 131 @Test testStat()132 public void testStat() throws Exception { 133 withTestFile(path -> { 134 var attr = Files.readAttributes(path, PosixFileAttributes.class); 135 var stat = Os.stat(path.toAbsolutePath().toString()); 136 assertAttributesEqual(attr, stat); 137 }); 138 } 139 140 @Test testLstat()141 public void testLstat() throws Exception { 142 withTestFile(path -> { 143 // Create a symbolic link 144 var lnk = Files.createTempFile("osTest", "lnk"); 145 Files.delete(lnk); 146 Files.createSymbolicLink(lnk, path); 147 148 // Test lstat 149 var attr = Files.readAttributes(lnk, PosixFileAttributes.class, NOFOLLOW_LINKS); 150 var stat = Os.lstat(lnk.toAbsolutePath().toString()); 151 assertAttributesEqual(attr, stat); 152 153 // Test stat 154 var followAttr = Files.readAttributes(lnk, PosixFileAttributes.class); 155 var followStat = Os.stat(lnk.toAbsolutePath().toString()); 156 assertAttributesEqual(followAttr, followStat); 157 }); 158 } 159 160 @Test testFstat()161 public void testFstat() throws Exception { 162 withTestFile(path -> { 163 var attr = Files.readAttributes(path, PosixFileAttributes.class); 164 try (var raf = new RandomAccessFile(path.toFile(), "r")) { 165 var fd = raf.getFD(); 166 var stat = Os.fstat(fd); 167 assertAttributesEqual(attr, stat); 168 } 169 }); 170 } 171 172 private static class TestThread extends Thread { 173 174 final CountDownLatch mLatch = new CountDownLatch(1); 175 int mTid; 176 TestThread()177 TestThread() { 178 setDaemon(true); 179 } 180 181 @Override run()182 public void run() { 183 mTid = Os.gettid(); 184 mLatch.countDown(); 185 } 186 } 187 188 @Test testGetTid()189 public void testGetTid() throws InterruptedException { 190 var t1 = new TestThread(); 191 var t2 = new TestThread(); 192 t1.start(); 193 t2.start(); 194 // Wait for thread execution 195 assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS)); 196 assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS)); 197 // Make sure the tid is unique per-thread 198 assertNotEquals(t1.mTid, t2.mTid); 199 } 200 201 // Verify StructStat values from libcore against native JVM PosixFileAttributes assertAttributesEqual(PosixFileAttributes attr, StructStat stat)202 private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) { 203 assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim)); 204 assertEquals(attr.size(), stat.st_size); 205 assertEquals(attr.isDirectory(), S_ISDIR(stat.st_mode)); 206 assertEquals(attr.isRegularFile(), S_ISREG(stat.st_mode)); 207 assertEquals(attr.isSymbolicLink(), S_ISLNK(stat.st_mode)); 208 assertEquals(attr.isOther(), S_ISCHR(stat.st_mode) 209 || S_ISBLK(stat.st_mode) || S_ISFIFO(stat.st_mode) || S_ISSOCK(stat.st_mode)); 210 assertEquals(attr.permissions(), convertModeToPosixPerms(stat.st_mode)); 211 212 } 213 convertTimespecToFileTime(StructTimespec ts)214 private static FileTime convertTimespecToFileTime(StructTimespec ts) { 215 var nanos = TimeUnit.SECONDS.toNanos(ts.tv_sec); 216 nanos += ts.tv_nsec; 217 return FileTime.from(nanos, TimeUnit.NANOSECONDS); 218 } 219 convertModeToPosixPerms(int mode)220 private static Set<PosixFilePermission> convertModeToPosixPerms(int mode) { 221 var set = new HashSet<PosixFilePermission>(); 222 if ((mode & OsConstants.S_IRUSR) != 0) set.add(PosixFilePermission.OWNER_READ); 223 if ((mode & OsConstants.S_IWUSR) != 0) set.add(PosixFilePermission.OWNER_WRITE); 224 if ((mode & OsConstants.S_IXUSR) != 0) set.add(PosixFilePermission.OWNER_EXECUTE); 225 if ((mode & OsConstants.S_IRGRP) != 0) set.add(PosixFilePermission.GROUP_READ); 226 if ((mode & OsConstants.S_IWGRP) != 0) set.add(PosixFilePermission.GROUP_WRITE); 227 if ((mode & OsConstants.S_IXGRP) != 0) set.add(PosixFilePermission.GROUP_EXECUTE); 228 if ((mode & OsConstants.S_IROTH) != 0) set.add(PosixFilePermission.OTHERS_READ); 229 if ((mode & OsConstants.S_IWOTH) != 0) set.add(PosixFilePermission.OTHERS_WRITE); 230 if ((mode & OsConstants.S_IXOTH) != 0) set.add(PosixFilePermission.OTHERS_EXECUTE); 231 return set; 232 } 233 write(FileDescriptor fd, int oneByte)234 private static void write(FileDescriptor fd, int oneByte) throws Exception { 235 // Create a dup to avoid closing the FD. 236 try (var dup = new FileOutputStream(Os.dup(fd))) { 237 dup.write(oneByte); 238 } 239 } 240 read(FileDescriptor fd)241 private static int read(FileDescriptor fd) throws Exception { 242 // Create a dup to avoid closing the FD. 243 try (var dup = new FileInputStream(Os.dup(fd))) { 244 return dup.read(); 245 } 246 } 247 checkAreDup(FileDescriptor fd1, FileDescriptor fd2)248 private static void checkAreDup(FileDescriptor fd1, FileDescriptor fd2) throws Exception { 249 assertEquals(4, Os.lseek(fd1, 4, OsConstants.SEEK_SET)); 250 assertEquals(4, Os.lseek(fd1, 0, OsConstants.SEEK_CUR)); 251 252 // Dup'ed FD shares the same position. 253 assertEquals(4, Os.lseek(fd2, 0, OsConstants.SEEK_CUR)); 254 255 assertEquals(6, Os.lseek(fd1, 2, OsConstants.SEEK_CUR)); 256 assertEquals(6, Os.lseek(fd2, 0, OsConstants.SEEK_CUR)); 257 } 258 } 259