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