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 FileDescriptor fd = getFileDescriptor(); 277 return fd != null ? new ParcelFileDescriptor(fd) : null; 278 } 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 { 304 return (native_get_size(fd) >= 0); 305 } 306 307 /** 308 * 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. 310 * 311 * @throws IOException If <code>fd</code> is not a valid file descriptor. 312 * 313 * @hide 314 */ 315 public static int getSize(FileDescriptor fd) throws IOException { 316 return native_get_size(fd); 317 } 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 333 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) { 369 return -1; 370 } 371 return mSingleByte[0]; 372 } 373 374 @Override 375 public int read(byte buffer[], int offset, int count) throws IOException { 376 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 } 385 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} 422