MemoryFile.java revision 7bcbd511731e13b9f2778e6aa6c633417d266f5e
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
17package android.os;
18
19import android.util.Log;
20
21import java.io.FileDescriptor;
22import java.io.IOException;
23import java.io.InputStream;
24import 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 */
36public 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
44    private static native FileDescriptor native_open(String name, int length) throws IOException;
45    // returns memory address for ashmem region
46    private static native int native_mmap(FileDescriptor fd, int length, int mode)
47            throws IOException;
48    private static native void native_munmap(int addr, int length) throws IOException;
49    private static native void native_close(FileDescriptor fd);
50    private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
51            int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
52    private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
53            int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
54    private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
55    private static native int native_get_size(FileDescriptor fd) throws IOException;
56
57    private FileDescriptor mFD;        // ashmem file descriptor
58    private int 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    private final boolean mOwnsRegion;  // false if this is a ref to an existing ashmem region
62
63    /**
64     * Allocates a new ashmem region. The region is initially not purgable.
65     *
66     * @param name optional name for the file (can be null).
67     * @param length of the memory file in bytes.
68     * @throws IOException if the memory file could not be created.
69     */
70    public MemoryFile(String name, int length) throws IOException {
71        mLength = length;
72        mFD = native_open(name, length);
73        mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
74        mOwnsRegion = true;
75    }
76
77    /**
78     * Creates a reference to an existing memory file. Changes to the original file
79     * will be available through this reference.
80     * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
81     *
82     * @param fd File descriptor for an existing memory file, as returned by
83     *        {@link #getFileDescriptor()}. This file descriptor will be closed
84     *        by {@link #close()}.
85     * @param length Length of the memory file in bytes.
86     * @param mode File mode. Currently only "r" for read-only access is supported.
87     * @throws NullPointerException if <code>fd</code> is null.
88     * @throws IOException If <code>fd</code> does not refer to an existing memory file,
89     *         or if the file mode of the existing memory file is more restrictive
90     *         than <code>mode</code>.
91     *
92     * @hide
93     */
94    public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
95        if (fd == null) {
96            throw new NullPointerException("File descriptor is null.");
97        }
98        if (!isMemoryFile(fd)) {
99            throw new IllegalArgumentException("Not a memory file.");
100        }
101        mLength = length;
102        mFD = fd;
103        mAddress = native_mmap(mFD, length, modeToProt(mode));
104        mOwnsRegion = false;
105    }
106
107    /**
108     * Closes the memory file. If there are no other open references to the memory
109     * file, it will be deleted.
110     */
111    public void close() {
112        deactivate();
113        if (!isClosed()) {
114            native_close(mFD);
115        }
116    }
117
118    /**
119     * Unmaps the memory file from the process's memory space, but does not close it.
120     * After this method has been called, read and write operations through this object
121     * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
122     *
123     * @hide
124     */
125    public void deactivate() {
126        if (!isDeactivated()) {
127            try {
128                native_munmap(mAddress, mLength);
129                mAddress = 0;
130            } catch (IOException ex) {
131                Log.e(TAG, ex.toString());
132            }
133        }
134    }
135
136    /**
137     * Checks whether the memory file has been deactivated.
138     */
139    private boolean isDeactivated() {
140        return mAddress == 0;
141    }
142
143    /**
144     * Checks whether the memory file has been closed.
145     */
146    private boolean isClosed() {
147        return !mFD.valid();
148    }
149
150    @Override
151    protected void finalize() {
152        if (!isClosed()) {
153            Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");
154            close();
155        }
156    }
157
158    /**
159     * Returns the length of the memory file.
160     *
161     * @return file length.
162     */
163    public int length() {
164        return mLength;
165    }
166
167    /**
168     * Is memory file purging enabled?
169     *
170     * @return true if the file may be purged.
171     */
172    public boolean isPurgingAllowed() {
173        return mAllowPurging;
174    }
175
176    /**
177     * Enables or disables purging of the memory file.
178     *
179     * @param allowPurging true if the operating system can purge the contents
180     * of the file in low memory situations
181     * @return previous value of allowPurging
182     */
183    synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
184        if (!mOwnsRegion) {
185            throw new IOException("Only the owner can make ashmem regions purgable.");
186        }
187        boolean oldValue = mAllowPurging;
188        if (oldValue != allowPurging) {
189            native_pin(mFD, !allowPurging);
190            mAllowPurging = allowPurging;
191        }
192        return oldValue;
193    }
194
195    /**
196     * Creates a new InputStream for reading from the memory file.
197     *
198     @return InputStream
199     */
200    public InputStream getInputStream() {
201        return new MemoryInputStream();
202    }
203
204    /**
205     * Creates a new OutputStream for writing to the memory file.
206     *
207     @return OutputStream
208     */
209     public OutputStream getOutputStream() {
210        return new MemoryOutputStream();
211    }
212
213    /**
214     * Reads bytes from the memory file.
215     * Will throw an IOException if the file has been purged.
216     *
217     * @param buffer byte array to read bytes into.
218     * @param srcOffset offset into the memory file to read from.
219     * @param destOffset offset into the byte array buffer to read into.
220     * @param count number of bytes to read.
221     * @return number of bytes read.
222     * @throws IOException if the memory file has been purged or deactivated.
223     */
224    public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
225            throws IOException {
226        if (isDeactivated()) {
227            throw new IOException("Can't read from deactivated memory file.");
228        }
229        if (destOffset < 0 || destOffset > buffer.length || count < 0
230                || count > buffer.length - destOffset
231                || srcOffset < 0 || srcOffset > mLength
232                || count > mLength - srcOffset) {
233            throw new IndexOutOfBoundsException();
234        }
235        return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
236    }
237
238    /**
239     * Write bytes to the memory file.
240     * Will throw an IOException if the file has been purged.
241     *
242     * @param buffer byte array to write bytes from.
243     * @param srcOffset offset into the byte array buffer to write from.
244     * @param destOffset offset  into the memory file to write to.
245     * @param count number of bytes to write.
246     * @throws IOException if the memory file has been purged or deactivated.
247     */
248    public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
249            throws IOException {
250        if (isDeactivated()) {
251            throw new IOException("Can't write to deactivated memory file.");
252        }
253        if (srcOffset < 0 || srcOffset > buffer.length || count < 0
254                || count > buffer.length - srcOffset
255                || destOffset < 0 || destOffset > mLength
256                || count > mLength - destOffset) {
257            throw new IndexOutOfBoundsException();
258        }
259        native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
260    }
261
262    /**
263     * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
264     * for caveats. This must be here to allow classes outside <code>android.os</code< to
265     * make ParcelFileDescriptors from MemoryFiles, as
266     * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
267     *
268     *
269     * @return The file descriptor owned by this memory file object.
270     *         The file descriptor is not duplicated.
271     * @throws IOException If the memory file has been closed.
272     *
273     * @hide
274     */
275    public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
276        return new ParcelFileDescriptor(getFileDescriptor());
277    }
278
279    /**
280     * Gets a FileDescriptor for the memory file. Note that this file descriptor
281     * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
282     * should not be used with file descriptor operations that expect a file descriptor
283     * for a normal file.
284     *
285     * The returned file descriptor is not duplicated.
286     *
287     * @throws IOException If the memory file has been closed.
288     *
289     * @hide
290     */
291    public FileDescriptor getFileDescriptor() throws IOException {
292        return mFD;
293    }
294
295    /**
296     * Checks whether the given file descriptor refers to a memory file.
297     *
298     * @throws IOException If <code>fd</code> is not a valid file descriptor.
299     *
300     * @hide
301     */
302    public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
303        return (native_get_size(fd) >= 0);
304    }
305
306    /**
307     * Returns the size of the memory file that the file descriptor refers to,
308     * or -1 if the file descriptor does not refer to a memory file.
309     *
310     * @throws IOException If <code>fd</code> is not a valid file descriptor.
311     *
312     * @hide
313     */
314    public static int getSize(FileDescriptor fd) throws IOException {
315        return native_get_size(fd);
316    }
317
318    /**
319     * Converts a file mode string to a <code>prot</code> value as expected by
320     * native_mmap().
321     *
322     * @throws IllegalArgumentException if the file mode is invalid.
323     */
324    private static int modeToProt(String mode) {
325        if ("r".equals(mode)) {
326            return PROT_READ;
327        } else {
328            throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
329        }
330    }
331
332    private class MemoryInputStream extends InputStream {
333
334        private int mMark = 0;
335        private int mOffset = 0;
336        private byte[] mSingleByte;
337
338        @Override
339        public int available() throws IOException {
340            if (mOffset >= mLength) {
341                return 0;
342            }
343            return mLength - mOffset;
344        }
345
346        @Override
347        public boolean markSupported() {
348            return true;
349        }
350
351        @Override
352        public void mark(int readlimit) {
353            mMark = mOffset;
354        }
355
356        @Override
357        public void reset() throws IOException {
358            mOffset = mMark;
359        }
360
361        @Override
362        public int read() throws IOException {
363            if (mSingleByte == null) {
364                mSingleByte = new byte[1];
365            }
366            int result = read(mSingleByte, 0, 1);
367            if (result != 1) {
368                return -1;
369            }
370            return mSingleByte[0];
371        }
372
373        @Override
374        public int read(byte buffer[], int offset, int count) throws IOException {
375            if (offset < 0 || count < 0 || offset + count > buffer.length) {
376                // readBytes() also does this check, but we need to do it before
377                // changing count.
378                throw new IndexOutOfBoundsException();
379            }
380            count = Math.min(count, available());
381            if (count < 1) {
382                return -1;
383            }
384            int result = readBytes(buffer, mOffset, offset, count);
385            if (result > 0) {
386                mOffset += result;
387            }
388            return result;
389        }
390
391        @Override
392        public long skip(long n) throws IOException {
393            if (mOffset + n > mLength) {
394                n = mLength - mOffset;
395            }
396            mOffset += n;
397            return n;
398        }
399    }
400
401    private class MemoryOutputStream extends OutputStream {
402
403        private int mOffset = 0;
404        private byte[] mSingleByte;
405
406        @Override
407        public void write(byte buffer[], int offset, int count) throws IOException {
408            writeBytes(buffer, offset, mOffset, count);
409        }
410
411        @Override
412        public void write(int oneByte) throws IOException {
413            if (mSingleByte == null) {
414                mSingleByte = new byte[1];
415            }
416            mSingleByte[0] = (byte)oneByte;
417            write(mSingleByte, 0, 1);
418        }
419    }
420}
421