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 
17 package android.os;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.system.ErrnoException;
21 
22 import java.io.FileDescriptor;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.nio.ByteBuffer;
27 
28 
29 /**
30  * MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable.
31  *
32  * Applications should generally prefer to use {@link SharedMemory} which offers more flexible
33  * access & control over the shared memory region than MemoryFile does.
34  *
35  * Purgeable files may have their contents reclaimed by the kernel
36  * in low memory conditions (only if allowPurging is set to true).
37  * After a file is purged, attempts to read or write the file will
38  * cause an IOException to be thrown.
39  */
40 public class MemoryFile {
41     private static String TAG = "MemoryFile";
42 
43     // Returns 'true' if purged, 'false' otherwise
44     @UnsupportedAppUsage
native_pin(FileDescriptor fd, boolean pin)45     private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException;
46     @UnsupportedAppUsage
native_get_size(FileDescriptor fd)47     private static native int native_get_size(FileDescriptor fd) throws IOException;
48 
49     private SharedMemory mSharedMemory;
50     private ByteBuffer mMapping;
51     private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
52 
53     /**
54      * Allocates a new ashmem region. The region is initially not purgable.
55      *
56      * @param name optional name for the file (can be null).
57      * @param length of the memory file in bytes, must be positive.
58      * @throws IOException if the memory file could not be created.
59      */
MemoryFile(String name, int length)60     public MemoryFile(String name, int length) throws IOException {
61         try {
62             mSharedMemory = SharedMemory.create(name, length);
63             mMapping = mSharedMemory.mapReadWrite();
64         } catch (ErrnoException ex) {
65             ex.rethrowAsIOException();
66         }
67     }
68 
69     /**
70      * Closes the memory file. If there are no other open references to the memory
71      * file, it will be deleted.
72      */
close()73     public void close() {
74         deactivate();
75         mSharedMemory.close();
76     }
77 
78     /**
79      * Unmaps the memory file from the process's memory space, but does not close it.
80      * After this method has been called, read and write operations through this object
81      * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
82      *
83      * @hide
84      */
85     @UnsupportedAppUsage
deactivate()86     void deactivate() {
87         if (mMapping != null) {
88             SharedMemory.unmap(mMapping);
89             mMapping = null;
90         }
91     }
92 
checkActive()93     private void checkActive() throws IOException {
94         if (mMapping == null) {
95             throw new IOException("MemoryFile has been deactivated");
96         }
97     }
98 
beginAccess()99     private void beginAccess() throws IOException {
100         checkActive();
101         if (mAllowPurging) {
102             if (native_pin(mSharedMemory.getFileDescriptor(), true)) {
103                 throw new IOException("MemoryFile has been purged");
104             }
105         }
106     }
107 
endAccess()108     private void endAccess() throws IOException {
109         if (mAllowPurging) {
110             native_pin(mSharedMemory.getFileDescriptor(), false);
111         }
112     }
113 
114     /**
115      * Returns the length of the memory file.
116      *
117      * @return file length.
118      */
length()119     public int length() {
120         return mSharedMemory.getSize();
121     }
122 
123     /**
124      * Is memory file purging enabled?
125      *
126      * @return true if the file may be purged.
127      *
128      * @deprecated Purgable is considered generally fragile and hard to use safely. Applications
129      * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
130      * to react to memory events and release shared memory regions as appropriate.
131      */
132     @Deprecated
isPurgingAllowed()133     public boolean isPurgingAllowed() {
134         return mAllowPurging;
135     }
136 
137     /**
138      * Enables or disables purging of the memory file.
139      *
140      * @param allowPurging true if the operating system can purge the contents
141      * of the file in low memory situations
142      * @return previous value of allowPurging
143      *
144      * @deprecated Purgable is considered generally fragile and hard to use safely. Applications
145      * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
146      * to react to memory events and release shared memory regions as appropriate.
147      */
148     @Deprecated
allowPurging(boolean allowPurging)149     synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
150         boolean oldValue = mAllowPurging;
151         if (oldValue != allowPurging) {
152             native_pin(mSharedMemory.getFileDescriptor(), !allowPurging);
153             mAllowPurging = allowPurging;
154         }
155         return oldValue;
156     }
157 
158     /**
159      * Creates a new InputStream for reading from the memory file.
160      *
161      @return InputStream
162      */
getInputStream()163     public InputStream getInputStream() {
164         return new MemoryInputStream();
165     }
166 
167     /**
168      * Creates a new OutputStream for writing to the memory file.
169      *
170      @return OutputStream
171      */
getOutputStream()172      public OutputStream getOutputStream() {
173         return new MemoryOutputStream();
174     }
175 
176     /**
177      * Reads bytes from the memory file.
178      * Will throw an IOException if the file has been purged.
179      *
180      * @param buffer byte array to read bytes into.
181      * @param srcOffset offset into the memory file to read from.
182      * @param destOffset offset into the byte array buffer to read into.
183      * @param count number of bytes to read.
184      * @return number of bytes read.
185      * @throws IOException if the memory file has been purged or deactivated.
186      */
readBytes(byte[] buffer, int srcOffset, int destOffset, int count)187     public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
188             throws IOException {
189         beginAccess();
190         try {
191             mMapping.position(srcOffset);
192             mMapping.get(buffer, destOffset, count);
193         } finally {
194             endAccess();
195         }
196         return count;
197     }
198 
199     /**
200      * Write bytes to the memory file.
201      * Will throw an IOException if the file has been purged.
202      *
203      * @param buffer byte array to write bytes from.
204      * @param srcOffset offset into the byte array buffer to write from.
205      * @param destOffset offset  into the memory file to write to.
206      * @param count number of bytes to write.
207      * @throws IOException if the memory file has been purged or deactivated.
208      */
writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)209     public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
210             throws IOException {
211         beginAccess();
212         try {
213             mMapping.position(destOffset);
214             mMapping.put(buffer, srcOffset, count);
215         } finally {
216             endAccess();
217         }
218     }
219 
220     /**
221      * Gets a FileDescriptor for the memory file.
222      *
223      * The returned file descriptor is not duplicated.
224      *
225      * @throws IOException If the memory file has been closed.
226      *
227      * @hide
228      */
229     @UnsupportedAppUsage
getFileDescriptor()230     public FileDescriptor getFileDescriptor() throws IOException {
231         return mSharedMemory.getFileDescriptor();
232     }
233 
234     /**
235      * Returns the size of the memory file that the file descriptor refers to,
236      * or -1 if the file descriptor does not refer to a memory file.
237      *
238      * @throws IOException If <code>fd</code> is not a valid file descriptor.
239      *
240      * @hide
241      */
242     @UnsupportedAppUsage
getSize(FileDescriptor fd)243     public static int getSize(FileDescriptor fd) throws IOException {
244         return native_get_size(fd);
245     }
246 
247     private class MemoryInputStream extends InputStream {
248 
249         private int mMark = 0;
250         private int mOffset = 0;
251         private byte[] mSingleByte;
252 
253         @Override
available()254         public int available() throws IOException {
255             if (mOffset >= mSharedMemory.getSize()) {
256                 return 0;
257             }
258             return mSharedMemory.getSize() - mOffset;
259         }
260 
261         @Override
markSupported()262         public boolean markSupported() {
263             return true;
264         }
265 
266         @Override
mark(int readlimit)267         public void mark(int readlimit) {
268             mMark = mOffset;
269         }
270 
271         @Override
reset()272         public void reset() throws IOException {
273             mOffset = mMark;
274         }
275 
276         @Override
read()277         public int read() throws IOException {
278             if (mSingleByte == null) {
279                 mSingleByte = new byte[1];
280             }
281             int result = read(mSingleByte, 0, 1);
282             if (result != 1) {
283                 return -1;
284             }
285             return mSingleByte[0];
286         }
287 
288         @Override
read(byte buffer[], int offset, int count)289         public int read(byte buffer[], int offset, int count) throws IOException {
290             if (offset < 0 || count < 0 || offset + count > buffer.length) {
291                 // readBytes() also does this check, but we need to do it before
292                 // changing count.
293                 throw new IndexOutOfBoundsException();
294             }
295             count = Math.min(count, available());
296             if (count < 1) {
297                 return -1;
298             }
299             int result = readBytes(buffer, mOffset, offset, count);
300             if (result > 0) {
301                 mOffset += result;
302             }
303             return result;
304         }
305 
306         @Override
skip(long n)307         public long skip(long n) throws IOException {
308             if (mOffset + n > mSharedMemory.getSize()) {
309                 n = mSharedMemory.getSize() - mOffset;
310             }
311             mOffset += n;
312             return n;
313         }
314     }
315 
316     private class MemoryOutputStream extends OutputStream {
317 
318         private int mOffset = 0;
319         private byte[] mSingleByte;
320 
321         @Override
write(byte buffer[], int offset, int count)322         public void write(byte buffer[], int offset, int count) throws IOException {
323             writeBytes(buffer, offset, mOffset, count);
324             mOffset += count;
325         }
326 
327         @Override
write(int oneByte)328         public void write(int oneByte) throws IOException {
329             if (mSingleByte == null) {
330                 mSingleByte = new byte[1];
331             }
332             mSingleByte[0] = (byte)oneByte;
333             write(mSingleByte, 0, 1);
334         }
335     }
336 }
337