blob: a81e16bacfb2dcfa00a9eec991490384066b55e3 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Bjorn Bringert761e0912009-05-29 11:46:12 +010021import java.io.FileDescriptor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import 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";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
Bjorn Bringert963cd0062009-05-29 14:05:12 +010040 // 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
Bjorn Bringert761e0912009-05-29 11:46:12 +010044 private static native FileDescriptor native_open(String name, int length) throws IOException;
45 // returns memory address for ashmem region
Bjorn Bringert963cd0062009-05-29 14:05:12 +010046 private static native int native_mmap(FileDescriptor fd, int length, int mode)
47 throws IOException;
Bjorn Bringert761e0912009-05-29 11:46:12 +010048 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;
Marco Nelissen7bcbd512009-06-23 10:34:55 -070055 private static native int native_get_size(FileDescriptor fd) throws IOException;
Bjorn Bringert761e0912009-05-29 11:46:12 +010056
57 private FileDescriptor mFD; // ashmem file descriptor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 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
Bjorn Bringert963cd0062009-05-29 14:05:12 +010061 private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
63 /**
Bjorn Bringert963cd0062009-05-29 14:05:12 +010064 * Allocates a new ashmem region. The region is initially not purgable.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 *
66 * @param name optional name for the file (can be null).
67 * @param length of the memory file in bytes.
Bjorn Bringert9fc2e9c2009-05-28 14:48:32 +010068 * @throws IOException if the memory file could not be created.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 */
Bjorn Bringert9fc2e9c2009-05-28 14:48:32 +010070 public MemoryFile(String name, int length) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 mLength = length;
72 mFD = native_open(name, length);
Bjorn Bringert963cd0062009-05-29 14:05:12 +010073 mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
74 mOwnsRegion = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 }
76
77 /**
Bjorn Bringert963cd0062009-05-29 14:05:12 +010078 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 */
111 public void close() {
Bjorn Bringert761e0912009-05-29 11:46:12 +0100112 deactivate();
113 if (!isClosed()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 native_close(mFD);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 }
116 }
117
Bjorn Bringert963cd0062009-05-29 14:05:12 +0100118 /**
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() {
Bjorn Bringert761e0912009-05-29 11:46:12 +0100126 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 @Override
151 protected void finalize() {
Bjorn Bringert761e0912009-05-29 11:46:12 +0100152 if (!isClosed()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 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 {
Bjorn Bringert963cd0062009-05-29 14:05:12 +0100184 if (!mOwnsRegion) {
185 throw new IOException("Only the owner can make ashmem regions purgable.");
186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 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() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 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.
Bjorn Bringert761e0912009-05-29 11:46:12 +0100222 * @throws IOException if the memory file has been purged or deactivated.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 */
224 public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
225 throws IOException {
Bjorn Bringert761e0912009-05-29 11:46:12 +0100226 if (isDeactivated()) {
227 throw new IOException("Can't read from deactivated memory file.");
228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 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.
Bjorn Bringert761e0912009-05-29 11:46:12 +0100246 * @throws IOException if the memory file has been purged or deactivated.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 */
248 public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
249 throws IOException {
Bjorn Bringert761e0912009-05-29 11:46:12 +0100250 if (isDeactivated()) {
251 throw new IOException("Can't write to deactivated memory file.");
252 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 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
Bjorn Bringert963cd0062009-05-29 14:05:12 +0100262 /**
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 {
Dianne Hackborn18668392010-03-23 22:10:55 -0700276 FileDescriptor fd = getFileDescriptor();
277 return fd != null ? new ParcelFileDescriptor(fd) : null;
Bjorn Bringert963cd0062009-05-29 14:05:12 +0100278 }
279
280 /**
281 * Gets a FileDescriptor for the memory file. Note that this file descriptor
282 * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
283 * should not be used with file descriptor operations that expect a file descriptor
284 * for a normal file.
285 *
286 * The returned file descriptor is not duplicated.
287 *
288 * @throws IOException If the memory file has been closed.
289 *
290 * @hide
291 */
292 public FileDescriptor getFileDescriptor() throws IOException {
293 return mFD;
294 }
295
296 /**
297 * Checks whether the given file descriptor refers to a memory file.
298 *
299 * @throws IOException If <code>fd</code> is not a valid file descriptor.
300 *
301 * @hide
302 */
303 public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
Marco Nelissen7bcbd512009-06-23 10:34:55 -0700304 return (native_get_size(fd) >= 0);
Marco Nelissenec100902009-06-17 08:56:59 -0700305 }
306
307 /**
Marco Nelissen7bcbd512009-06-23 10:34:55 -0700308 * Returns the size of the memory file that the file descriptor refers to,
309 * or -1 if the file descriptor does not refer to a memory file.
Marco Nelissenec100902009-06-17 08:56:59 -0700310 *
311 * @throws IOException If <code>fd</code> is not a valid file descriptor.
312 *
313 * @hide
314 */
Marco Nelissen7bcbd512009-06-23 10:34:55 -0700315 public static int getSize(FileDescriptor fd) throws IOException {
316 return native_get_size(fd);
Bjorn Bringert963cd0062009-05-29 14:05:12 +0100317 }
318
319 /**
320 * Converts a file mode string to a <code>prot</code> value as expected by
321 * native_mmap().
322 *
323 * @throws IllegalArgumentException if the file mode is invalid.
324 */
325 private static int modeToProt(String mode) {
326 if ("r".equals(mode)) {
327 return PROT_READ;
328 } else {
329 throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
330 }
331 }
332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 private class MemoryInputStream extends InputStream {
334
335 private int mMark = 0;
336 private int mOffset = 0;
337 private byte[] mSingleByte;
338
339 @Override
340 public int available() throws IOException {
341 if (mOffset >= mLength) {
342 return 0;
343 }
344 return mLength - mOffset;
345 }
346
347 @Override
348 public boolean markSupported() {
349 return true;
350 }
351
352 @Override
353 public void mark(int readlimit) {
354 mMark = mOffset;
355 }
356
357 @Override
358 public void reset() throws IOException {
359 mOffset = mMark;
360 }
361
362 @Override
363 public int read() throws IOException {
364 if (mSingleByte == null) {
365 mSingleByte = new byte[1];
366 }
367 int result = read(mSingleByte, 0, 1);
368 if (result != 1) {
Bjorn Bringertc1823702009-06-01 10:53:06 +0100369 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371 return mSingleByte[0];
372 }
373
374 @Override
375 public int read(byte buffer[], int offset, int count) throws IOException {
Bjorn Bringertc1823702009-06-01 10:53:06 +0100376 if (offset < 0 || count < 0 || offset + count > buffer.length) {
377 // readBytes() also does this check, but we need to do it before
378 // changing count.
379 throw new IndexOutOfBoundsException();
380 }
381 count = Math.min(count, available());
382 if (count < 1) {
383 return -1;
384 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 int result = readBytes(buffer, mOffset, offset, count);
386 if (result > 0) {
387 mOffset += result;
388 }
389 return result;
390 }
391
392 @Override
393 public long skip(long n) throws IOException {
394 if (mOffset + n > mLength) {
395 n = mLength - mOffset;
396 }
397 mOffset += n;
398 return n;
399 }
400 }
401
402 private class MemoryOutputStream extends OutputStream {
403
404 private int mOffset = 0;
405 private byte[] mSingleByte;
406
407 @Override
408 public void write(byte buffer[], int offset, int count) throws IOException {
409 writeBytes(buffer, offset, mOffset, count);
410 }
411
412 @Override
413 public void write(int oneByte) throws IOException {
414 if (mSingleByte == null) {
415 mSingleByte = new byte[1];
416 }
417 mSingleByte[0] = (byte)oneByte;
418 write(mSingleByte, 0, 1);
419 }
420 }
421}