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