1 /*
2  * Copyright (C) 2010 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 
17 package libcore.io;
18 
19 import android.system.ErrnoException;
20 import android.system.StructStat;
21 import java.io.File;
22 import java.io.FileDescriptor;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.InterruptedIOException;
26 import java.net.Socket;
27 import java.nio.charset.Charset;
28 import java.nio.charset.StandardCharsets;
29 import java.util.Objects;
30 
31 import dalvik.annotation.compat.UnsupportedAppUsage;
32 import libcore.util.NonNull;
33 import static android.system.OsConstants.*;
34 
35 /** @hide */
36 @libcore.api.CorePlatformApi
37 public final class IoUtils {
IoUtils()38     private IoUtils() {
39     }
40 
41     /**
42      * Acquires ownership of an integer file descriptor from a FileDescriptor.
43      *
44      * This method invalidates the FileDescriptor passed in.
45      *
46      * The important part of this function is that you are taking ownership of a resource that you
47      * must either clean up yourself, or hand off to some other object that does that for you.
48      *
49      * See bionic/include/android/fdsan.h for more details.
50      *
51      * @param fd FileDescriptor to take ownership from, must be non-null.
52      * @throws NullPointerException if fd is null
53      */
54     @libcore.api.CorePlatformApi
acquireRawFd(@onNull FileDescriptor fd)55     public static int acquireRawFd(@NonNull FileDescriptor fd) {
56         Objects.requireNonNull(fd);
57 
58         FileDescriptor copy = fd.release$();
59         // Get the numeric Unix file descriptor. -1 means it is invalid; for example if
60         // {@link FileDescriptor#release$()} has already been called on the FileDescriptor.
61         int rawFd = copy.getInt$();
62         long previousOwnerId = copy.getOwnerId$();
63         if (rawFd != -1 && previousOwnerId != FileDescriptor.NO_OWNER) {
64           // Clear the file descriptor's owner ID, aborting if the previous value isn't as expected.
65           Libcore.os.android_fdsan_exchange_owner_tag(copy, previousOwnerId,
66                                                       FileDescriptor.NO_OWNER);
67         }
68         return rawFd;
69     }
70 
isParcelFileDescriptor(Object object)71     private static boolean isParcelFileDescriptor(Object object) {
72         // We need to look up ParcelFileDescriptor dynamically, because there are cases where the
73         // framework classes will not be found on the classpath such as on-host development.
74         try {
75             Class<?> pfdClass = Class.forName("android.os.ParcelFileDescriptor");
76             if (pfdClass.isInstance(object)) {
77                 return true;
78             }
79             return false;
80         } catch (ClassNotFoundException ex) {
81             return false;
82         }
83     }
84 
generateFdOwnerId(Object owner)85     private static long generateFdOwnerId(Object owner) {
86         if (owner == null) {
87             return 0;
88         }
89 
90         // Type values from bionic's <android/fdsan.h>.
91         long tagType;
92         if (owner instanceof java.io.FileInputStream) {
93             tagType = 5;
94         } else if (owner instanceof java.io.FileOutputStream) {
95             tagType = 6;
96         } else if (owner instanceof java.io.RandomAccessFile) {
97             tagType = 7;
98         } else if (owner instanceof java.net.DatagramSocketImpl) {
99             tagType = 10;
100         } else if (owner instanceof java.net.SocketImpl) {
101             tagType = 11;
102         } else if (isParcelFileDescriptor(owner)) {
103             tagType = 8;
104         } else {
105             // Generic Java type.
106             tagType = 255;
107         }
108 
109         // The owner ID is not required to be unique but should be stable and attempt to avoid
110         // collision with identifiers generated both here and in native code (which are simply the
111         // address of the owning object). identityHashCode(Object) meets these requirements.
112         long tagValue = System.identityHashCode(owner);
113         return tagType << 56 | tagValue;
114     }
115 
116     /**
117      * Assigns ownership of an unowned FileDescriptor.
118      *
119      * Associates the supplied FileDescriptor and the underlying Unix file descriptor with an owner
120      * ID derived from the supplied {@code owner} object. If the FileDescriptor already has an
121      * associated owner an {@link IllegalStateException} will be thrown. If the underlying Unix
122      * file descriptor already has an associated owner, the process will abort.
123      *
124      * See bionic/include/android/fdsan.h for more details.
125      *
126      * @param fd FileDescriptor to take ownership from, must be non-null.
127      * @throws NullPointerException if fd or owner are null
128      * @throws IllegalStateException if fd is already owned
129      */
130     @libcore.api.CorePlatformApi
setFdOwner(@onNull FileDescriptor fd, @NonNull Object owner)131     public static void setFdOwner(@NonNull FileDescriptor fd, @NonNull Object owner) {
132         Objects.requireNonNull(fd);
133         Objects.requireNonNull(owner);
134 
135         long previousOwnerId = fd.getOwnerId$();
136         if (previousOwnerId != FileDescriptor.NO_OWNER) {
137             throw new IllegalStateException("Attempted to take ownership of already-owned " +
138                                             "FileDescriptor");
139         }
140 
141         long ownerId = generateFdOwnerId(owner);
142         fd.setOwnerId$(ownerId);
143 
144         // Set the file descriptor's owner ID, aborting if the previous value isn't as expected.
145         Libcore.os.android_fdsan_exchange_owner_tag(fd, previousOwnerId, ownerId);
146     }
147 
148     /**
149      * Calls close(2) on 'fd'. Also resets the internal int to -1. Does nothing if 'fd' is null
150      * or invalid.
151      */
152     @libcore.api.CorePlatformApi
close(FileDescriptor fd)153     public static void close(FileDescriptor fd) throws IOException {
154         IoBridge.closeAndSignalBlockedThreads(fd);
155     }
156 
157     /**
158      * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
159      */
160     @UnsupportedAppUsage
161     @libcore.api.CorePlatformApi
closeQuietly(AutoCloseable closeable)162     public static void closeQuietly(AutoCloseable closeable) {
163         if (closeable != null) {
164             try {
165                 closeable.close();
166             } catch (RuntimeException rethrown) {
167                 throw rethrown;
168             } catch (Exception ignored) {
169             }
170         }
171     }
172 
173     /**
174      * Closes 'fd', ignoring any exceptions. Does nothing if 'fd' is null or invalid.
175      */
176     @UnsupportedAppUsage
177     @libcore.api.CorePlatformApi
closeQuietly(FileDescriptor fd)178     public static void closeQuietly(FileDescriptor fd) {
179         try {
180             IoUtils.close(fd);
181         } catch (IOException ignored) {
182         }
183     }
184 
185     /**
186      * Closes 'socket', ignoring any exceptions. Does nothing if 'socket' is null.
187      */
188     @UnsupportedAppUsage
189     @libcore.api.CorePlatformApi
closeQuietly(Socket socket)190     public static void closeQuietly(Socket socket) {
191         if (socket != null) {
192             try {
193                 socket.close();
194             } catch (Exception ignored) {
195             }
196         }
197     }
198 
199     /**
200      * Sets 'fd' to be blocking or non-blocking, according to the state of 'blocking'.
201      */
202     @UnsupportedAppUsage
203     @libcore.api.CorePlatformApi
setBlocking(FileDescriptor fd, boolean blocking)204     public static void setBlocking(FileDescriptor fd, boolean blocking) throws IOException {
205         try {
206             int flags = Libcore.os.fcntlVoid(fd, F_GETFL);
207             if (!blocking) {
208                 flags |= O_NONBLOCK;
209             } else {
210                 flags &= ~O_NONBLOCK;
211             }
212             Libcore.os.fcntlInt(fd, F_SETFL, flags);
213         } catch (ErrnoException errnoException) {
214             throw errnoException.rethrowAsIOException();
215         }
216     }
217 
218     /**
219      * Returns the contents of 'path' as a byte array.
220      */
221     @UnsupportedAppUsage
222     @libcore.api.CorePlatformApi
readFileAsByteArray(String absolutePath)223     public static byte[] readFileAsByteArray(String absolutePath) throws IOException {
224         return new FileReader(absolutePath).readFully().toByteArray();
225     }
226 
227     /**
228      * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
229      */
230     @UnsupportedAppUsage
231     @libcore.api.CorePlatformApi
readFileAsString(String absolutePath)232     public static String readFileAsString(String absolutePath) throws IOException {
233         return new FileReader(absolutePath).readFully().toString(StandardCharsets.UTF_8);
234     }
235 
236     /**
237      * Do not use. Use createTemporaryDirectory instead.
238      *
239      * Used by frameworks/base unit tests to clean up a temporary directory.
240      * Deliberately ignores errors, on the assumption that test cleanup is only
241      * supposed to be best-effort.
242      *
243      * @deprecated Use {@link TestIoUtils#createTemporaryDirectory} instead.
244      */
245     @libcore.api.CorePlatformApi
246     @Deprecated
deleteContents(File dir)247     public static void deleteContents(File dir) throws IOException {
248         File[] files = dir.listFiles();
249         if (files != null) {
250             for (File file : files) {
251                 if (file.isDirectory()) {
252                     deleteContents(file);
253                 }
254                 file.delete();
255             }
256         }
257     }
258 
259     /**
260      * Creates a unique new temporary directory under "java.io.tmpdir".
261      *
262      * @deprecated Use {@link TestIoUtils#createTemporaryDirectory} instead.
263      */
264     @libcore.api.CorePlatformApi
265     @Deprecated
createTemporaryDirectory(String prefix)266     public static File createTemporaryDirectory(String prefix) {
267         while (true) {
268             String candidateName = prefix + Math.randomIntInternal();
269             File result = new File(System.getProperty("java.io.tmpdir"), candidateName);
270             if (result.mkdir()) {
271                 return result;
272             }
273         }
274     }
275 
276     /**
277      * Do not use. This is for System.loadLibrary use only.
278      *
279      * Checks whether {@code path} can be opened read-only. Similar to File.exists, but doesn't
280      * require read permission on the parent, so it'll work in more cases, and allow you to
281      * remove read permission from more directories. Everyone else should just open(2) and then
282      * use the fd, but the loadLibrary API is broken by its need to ask ClassLoaders where to
283      * find a .so rather than just calling dlopen(3).
284      */
canOpenReadOnly(String path)285     public static boolean canOpenReadOnly(String path) {
286         try {
287             // Use open(2) rather than stat(2) so we require fewer permissions. http://b/6485312.
288             FileDescriptor fd = Libcore.os.open(path, O_RDONLY, 0);
289             Libcore.os.close(fd);
290             return true;
291         } catch (ErrnoException errnoException) {
292             return false;
293         }
294     }
295 
throwInterruptedIoException()296     public static void throwInterruptedIoException() throws InterruptedIOException {
297         // This is typically thrown in response to an
298         // InterruptedException which does not leave the thread in an
299         // interrupted state, so explicitly interrupt here.
300         Thread.currentThread().interrupt();
301         // TODO: set InterruptedIOException.bytesTransferred
302         throw new InterruptedIOException();
303     }
304 
305     /**
306      * A convenience class for reading the contents of a file into a {@code String}
307      * or a {@code byte[]}. This class attempts to minimize the number of allocations
308      * and copies required to read this data.
309      *
310      * For the case where we know the "true" length of a file (most ordinary files)
311      * we allocate exactly one byte[] and copy data into that. Calls to
312      * {@link #toByteArray} will then return the internal array and <b>not</b> a copy.
313      *
314      * <b>Note that an absolute path must be supplied. Expect your reads to fail
315      * if one isn't.</b>
316      */
317     private static class FileReader {
318         private FileDescriptor fd;
319         private boolean unknownLength;
320 
321         private byte[] bytes;
322         private int count;
323 
FileReader(String absolutePath)324         public FileReader(String absolutePath) throws IOException {
325             // We use IoBridge.open because callers might differentiate
326             // between a FileNotFoundException and a general IOException.
327             //
328             // NOTE: This costs us an additional call to fstat(2) to test whether
329             // "absolutePath" is a directory or not. We can eliminate it
330             // at the cost of copying some code from IoBridge.open.
331             try {
332                 fd = IoBridge.open(absolutePath, O_RDONLY);
333             } catch (FileNotFoundException fnfe) {
334                 throw fnfe;
335             }
336 
337             int capacity;
338             try {
339                 final StructStat stat = Libcore.os.fstat(fd);
340                 // Like RAF & other APIs, we assume that the file size fits
341                 // into a 32 bit integer.
342                 capacity = (int) stat.st_size;
343                 if (capacity == 0) {
344                     unknownLength = true;
345                     capacity = 8192;
346                 }
347             } catch (ErrnoException exception) {
348                 closeQuietly(fd);
349                 throw exception.rethrowAsIOException();
350             }
351 
352             bytes = new byte[capacity];
353         }
354 
readFully()355         public FileReader readFully() throws IOException {
356             int read;
357             int capacity = bytes.length;
358             try {
359                 while ((read = Libcore.os.read(fd, bytes, count, capacity - count)) != 0) {
360                     count += read;
361                     if (count == capacity) {
362                         if (unknownLength) {
363                             // If we don't know the length of this file, we need to continue
364                             // reading until we reach EOF. Double the capacity in preparation.
365                             final int newCapacity = capacity * 2;
366                             byte[] newBytes = new byte[newCapacity];
367                             System.arraycopy(bytes, 0, newBytes, 0, capacity);
368                             bytes = newBytes;
369                             capacity = newCapacity;
370                         } else {
371                             // We know the length of this file and we've read the right number
372                             // of bytes from it, return.
373                             break;
374                         }
375                     }
376                 }
377 
378                 return this;
379             } catch (ErrnoException e) {
380                 throw e.rethrowAsIOException();
381             } finally {
382                 closeQuietly(fd);
383             }
384         }
385 
386         @FindBugsSuppressWarnings("EI_EXPOSE_REP")
toByteArray()387         public byte[] toByteArray() {
388             if (count == bytes.length) {
389                 return bytes;
390             }
391             byte[] result = new byte[count];
392             System.arraycopy(bytes, 0, result, 0, count);
393             return result;
394         }
395 
toString(Charset cs)396         public String toString(Charset cs) {
397             return new String(bytes, 0, count, cs);
398         }
399     }
400 }
401