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 long native_mmap(FileDescriptor fd, int length, int mode)
47            throws IOException;
48    private static native void native_munmap(long addr, int length) throws IOException;
49    private static native void native_close(FileDescriptor fd);
50    private static native int native_read(FileDescriptor fd, long address, byte[] buffer,
51            int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
52    private static native void native_write(FileDescriptor fd, long 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 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     */
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     */
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     */
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     */
116    private boolean isDeactivated() {
117        return mAddress == 0;
118    }
119
120    /**
121     * Checks whether the memory file has been closed.
122     */
123    private boolean isClosed() {
124        return !mFD.valid();
125    }
126
127    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
268        public int available() throws IOException {
269            if (mOffset >= mLength) {
270                return 0;
271            }
272            return mLength - mOffset;
273        }
274
275        @Override
276        public boolean markSupported() {
277            return true;
278        }
279
280        @Override
281        public void mark(int readlimit) {
282            mMark = mOffset;
283        }
284
285        @Override
286        public void reset() throws IOException {
287            mOffset = mMark;
288        }
289
290        @Override
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
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
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
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
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