1 /*
2  * Copyright (C) 2008 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 android.os;
18 
19 import android.util.Log;
20 
21 import java.io.FileDescriptor;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 
26 
27 /**
28  * MemoryFile is a wrapper for the Linux ashmem driver.
29  * MemoryFiles are backed by shared memory, which can be optionally
30  * set to be purgeable.
31  * Purgeable files may have their contents reclaimed by the kernel
32  * in low memory conditions (only if allowPurging is set to true).
33  * After a file is purged, attempts to read or write the file will
34  * cause an IOException to be thrown.
35  */
36 public class MemoryFile
37 {
38     private static String TAG = "MemoryFile";
39 
40     // mmap(2) protection flags from <sys/mman.h>
41     private static final int PROT_READ = 0x1;
42     private static final int PROT_WRITE = 0x2;
43 
native_open(String name, int length)44     private static native FileDescriptor native_open(String name, int length) throws IOException;
45     // returns memory address for ashmem region
native_mmap(FileDescriptor fd, int length, int mode)46     private static native long native_mmap(FileDescriptor fd, int length, int mode)
47             throws IOException;
native_munmap(long addr, int length)48     private static native void native_munmap(long addr, int length) throws IOException;
native_close(FileDescriptor fd)49     private static native void native_close(FileDescriptor fd);
native_read(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)50     private static native int native_read(FileDescriptor fd, long address, byte[] buffer,
51             int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
native_write(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)52     private static native void native_write(FileDescriptor fd, long address, byte[] buffer,
53             int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
native_pin(FileDescriptor fd, boolean pin)54     private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
native_get_size(FileDescriptor fd)55     private static native int native_get_size(FileDescriptor fd) throws IOException;
56 
57     private FileDescriptor mFD;        // ashmem file descriptor
58     private long mAddress;   // address of ashmem memory
59     private int mLength;    // total length of our ashmem region
60     private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
61 
62     /**
63      * Allocates a new ashmem region. The region is initially not purgable.
64      *
65      * @param name optional name for the file (can be null).
66      * @param length of the memory file in bytes, must be non-negative.
67      * @throws IOException if the memory file could not be created.
68      */
MemoryFile(String name, int length)69     public MemoryFile(String name, int length) throws IOException {
70         mLength = length;
71         if (length >= 0) {
72             mFD = native_open(name, length);
73         } else {
74             throw new IOException("Invalid length: " + length);
75         }
76 
77         if (length > 0) {
78             mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
79         } else {
80             mAddress = 0;
81         }
82     }
83 
84     /**
85      * Closes the memory file. If there are no other open references to the memory
86      * file, it will be deleted.
87      */
close()88     public void close() {
89         deactivate();
90         if (!isClosed()) {
91             native_close(mFD);
92         }
93     }
94 
95     /**
96      * Unmaps the memory file from the process's memory space, but does not close it.
97      * After this method has been called, read and write operations through this object
98      * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
99      *
100      * @hide
101      */
deactivate()102     void deactivate() {
103         if (!isDeactivated()) {
104             try {
105                 native_munmap(mAddress, mLength);
106                 mAddress = 0;
107             } catch (IOException ex) {
108                 Log.e(TAG, ex.toString());
109             }
110         }
111     }
112 
113     /**
114      * Checks whether the memory file has been deactivated.
115      */
isDeactivated()116     private boolean isDeactivated() {
117         return mAddress == 0;
118     }
119 
120     /**
121      * Checks whether the memory file has been closed.
122      */
isClosed()123     private boolean isClosed() {
124         return !mFD.valid();
125     }
126 
127     @Override
finalize()128     protected void finalize() {
129         if (!isClosed()) {
130             Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");
131             close();
132         }
133     }
134 
135     /**
136      * Returns the length of the memory file.
137      *
138      * @return file length.
139      */
length()140     public int length() {
141         return mLength;
142     }
143 
144     /**
145      * Is memory file purging enabled?
146      *
147      * @return true if the file may be purged.
148      */
isPurgingAllowed()149     public boolean isPurgingAllowed() {
150         return mAllowPurging;
151     }
152 
153     /**
154      * Enables or disables purging of the memory file.
155      *
156      * @param allowPurging true if the operating system can purge the contents
157      * of the file in low memory situations
158      * @return previous value of allowPurging
159      */
allowPurging(boolean allowPurging)160     synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
161         boolean oldValue = mAllowPurging;
162         if (oldValue != allowPurging) {
163             native_pin(mFD, !allowPurging);
164             mAllowPurging = allowPurging;
165         }
166         return oldValue;
167     }
168 
169     /**
170      * Creates a new InputStream for reading from the memory file.
171      *
172      @return InputStream
173      */
getInputStream()174     public InputStream getInputStream() {
175         return new MemoryInputStream();
176     }
177 
178     /**
179      * Creates a new OutputStream for writing to the memory file.
180      *
181      @return OutputStream
182      */
getOutputStream()183      public OutputStream getOutputStream() {
184         return new MemoryOutputStream();
185     }
186 
187     /**
188      * Reads bytes from the memory file.
189      * Will throw an IOException if the file has been purged.
190      *
191      * @param buffer byte array to read bytes into.
192      * @param srcOffset offset into the memory file to read from.
193      * @param destOffset offset into the byte array buffer to read into.
194      * @param count number of bytes to read.
195      * @return number of bytes read.
196      * @throws IOException if the memory file has been purged or deactivated.
197      */
readBytes(byte[] buffer, int srcOffset, int destOffset, int count)198     public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
199             throws IOException {
200         if (isDeactivated()) {
201             throw new IOException("Can't read from deactivated memory file.");
202         }
203         if (destOffset < 0 || destOffset > buffer.length || count < 0
204                 || count > buffer.length - destOffset
205                 || srcOffset < 0 || srcOffset > mLength
206                 || count > mLength - srcOffset) {
207             throw new IndexOutOfBoundsException();
208         }
209         return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
210     }
211 
212     /**
213      * Write bytes to the memory file.
214      * Will throw an IOException if the file has been purged.
215      *
216      * @param buffer byte array to write bytes from.
217      * @param srcOffset offset into the byte array buffer to write from.
218      * @param destOffset offset  into the memory file to write to.
219      * @param count number of bytes to write.
220      * @throws IOException if the memory file has been purged or deactivated.
221      */
writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)222     public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
223             throws IOException {
224         if (isDeactivated()) {
225             throw new IOException("Can't write to deactivated memory file.");
226         }
227         if (srcOffset < 0 || srcOffset > buffer.length || count < 0
228                 || count > buffer.length - srcOffset
229                 || destOffset < 0 || destOffset > mLength
230                 || count > mLength - destOffset) {
231             throw new IndexOutOfBoundsException();
232         }
233         native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
234     }
235 
236     /**
237      * Gets a FileDescriptor for the memory file.
238      *
239      * The returned file descriptor is not duplicated.
240      *
241      * @throws IOException If the memory file has been closed.
242      *
243      * @hide
244      */
getFileDescriptor()245     public FileDescriptor getFileDescriptor() throws IOException {
246         return mFD;
247     }
248 
249     /**
250      * Returns the size of the memory file that the file descriptor refers to,
251      * or -1 if the file descriptor does not refer to a memory file.
252      *
253      * @throws IOException If <code>fd</code> is not a valid file descriptor.
254      *
255      * @hide
256      */
getSize(FileDescriptor fd)257     public static int getSize(FileDescriptor fd) throws IOException {
258         return native_get_size(fd);
259     }
260 
261     private class MemoryInputStream extends InputStream {
262 
263         private int mMark = 0;
264         private int mOffset = 0;
265         private byte[] mSingleByte;
266 
267         @Override
available()268         public int available() throws IOException {
269             if (mOffset >= mLength) {
270                 return 0;
271             }
272             return mLength - mOffset;
273         }
274 
275         @Override
markSupported()276         public boolean markSupported() {
277             return true;
278         }
279 
280         @Override
mark(int readlimit)281         public void mark(int readlimit) {
282             mMark = mOffset;
283         }
284 
285         @Override
reset()286         public void reset() throws IOException {
287             mOffset = mMark;
288         }
289 
290         @Override
read()291         public int read() throws IOException {
292             if (mSingleByte == null) {
293                 mSingleByte = new byte[1];
294             }
295             int result = read(mSingleByte, 0, 1);
296             if (result != 1) {
297                 return -1;
298             }
299             return mSingleByte[0];
300         }
301 
302         @Override
read(byte buffer[], int offset, int count)303         public int read(byte buffer[], int offset, int count) throws IOException {
304             if (offset < 0 || count < 0 || offset + count > buffer.length) {
305                 // readBytes() also does this check, but we need to do it before
306                 // changing count.
307                 throw new IndexOutOfBoundsException();
308             }
309             count = Math.min(count, available());
310             if (count < 1) {
311                 return -1;
312             }
313             int result = readBytes(buffer, mOffset, offset, count);
314             if (result > 0) {
315                 mOffset += result;
316             }
317             return result;
318         }
319 
320         @Override
skip(long n)321         public long skip(long n) throws IOException {
322             if (mOffset + n > mLength) {
323                 n = mLength - mOffset;
324             }
325             mOffset += n;
326             return n;
327         }
328     }
329 
330     private class MemoryOutputStream extends OutputStream {
331 
332         private int mOffset = 0;
333         private byte[] mSingleByte;
334 
335         @Override
write(byte buffer[], int offset, int count)336         public void write(byte buffer[], int offset, int count) throws IOException {
337             writeBytes(buffer, offset, mOffset, count);
338             mOffset += count;
339         }
340 
341         @Override
write(int oneByte)342         public void write(int oneByte) throws IOException {
343             if (mSingleByte == null) {
344                 mSingleByte = new byte[1];
345             }
346             mSingleByte[0] = (byte)oneByte;
347             write(mSingleByte, 0, 1);
348         }
349     }
350 }
351