1 /*
2  * Copyright (C) 2017 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.system.ErrnoException;
23 import android.system.Os;
24 import android.system.OsConstants;
25 
26 import dalvik.system.VMRuntime;
27 
28 import java.io.Closeable;
29 import java.io.FileDescriptor;
30 import java.nio.ByteBuffer;
31 import java.nio.DirectByteBuffer;
32 import java.nio.NioUtils;
33 
34 import sun.misc.Cleaner;
35 
36 /**
37  * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory.
38  */
39 public final class SharedMemory implements Parcelable, Closeable {
40 
41     private final FileDescriptor mFileDescriptor;
42     private final int mSize;
43     private final MemoryRegistration mMemoryRegistration;
44     private Cleaner mCleaner;
45 
SharedMemory(FileDescriptor fd)46     private SharedMemory(FileDescriptor fd) {
47         // This constructor is only used internally so it should be impossible to hit any of the
48         // exceptions unless something goes horribly wrong.
49         if (fd == null) {
50             throw new IllegalArgumentException(
51                     "Unable to create SharedMemory from a null FileDescriptor");
52         }
53         if (!fd.valid()) {
54             throw new IllegalArgumentException(
55                     "Unable to create SharedMemory from closed FileDescriptor");
56         }
57         mFileDescriptor = fd;
58         mSize = nGetSize(mFileDescriptor);
59         if (mSize <= 0) {
60             throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd");
61         }
62 
63         mMemoryRegistration = new MemoryRegistration(mSize);
64         mCleaner = Cleaner.create(mFileDescriptor,
65                 new Closer(mFileDescriptor, mMemoryRegistration));
66     }
67 
68     /**
69      * Creates an anonymous SharedMemory instance with the provided debug name and size. The name
70      * is only used for debugging purposes and can help identify what the shared memory is used
71      * for when inspecting memory maps for the processes that have mapped this SharedMemory
72      * instance.
73      *
74      * @param name The debug name to use for this SharedMemory instance. This can be null, however
75      *             a debug name is recommended to help identify memory usage when using tools
76      *             such as lsof or examining /proc/[pid]/maps
77      * @param size The size of the shared memory to create. Must be greater than 0.
78      * @return A SharedMemory instance of the requested size
79      * @throws ErrnoException if the requested allocation fails.
80      */
create(@ullable String name, int size)81     public static @NonNull SharedMemory create(@Nullable String name, int size)
82             throws ErrnoException {
83         if (size <= 0) {
84             throw new IllegalArgumentException("Size must be greater than zero");
85         }
86         return new SharedMemory(nCreate(name, size));
87     }
88 
checkOpen()89     private void checkOpen() {
90         if (!mFileDescriptor.valid()) {
91             throw new IllegalStateException("SharedMemory is closed");
92         }
93     }
94 
95     private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE
96             | OsConstants.PROT_EXEC | OsConstants.PROT_NONE;
97 
validateProt(int prot)98     private static void validateProt(int prot) {
99         if ((prot & ~PROT_MASK) != 0) {
100             throw new IllegalArgumentException("Invalid prot value");
101         }
102     }
103 
104     /**
105      * Sets the protection on the shared memory to the combination specified in prot, which
106      * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ},
107      * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC}
108      * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE},
109      * to remove all further access.
110      *
111      * Note that protection can only ever be removed, not added. By default shared memory
112      * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection
113      * passed here also only applies to any mappings created after calling this method. Existing
114      * mmaps of the shared memory retain whatever protection they had when they were created.
115      *
116      * A common usage of this is to share a read-only copy of the data with something else. To do
117      * that first create the read/write mapping with PROT_READ | PROT_WRITE,
118      * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory
119      * to another process. That process will only be able to mmap with PROT_READ.
120      *
121      * @param prot Any bitwise-or'ed combination of
122      *                  {@link android.system.OsConstants#PROT_READ},
123      *                  {@link android.system.OsConstants#PROT_WRITE}, and
124      *                  {@link android.system.OsConstants#PROT_EXEC}; or
125      *                  {@link android.system.OsConstants#PROT_NONE}
126      * @return Whether or not the requested protection was applied. Returns true on success,
127      * false if the requested protection was broader than the existing protection.
128      */
setProtect(int prot)129     public boolean setProtect(int prot) {
130         checkOpen();
131         validateProt(prot);
132         int errno = nSetProt(mFileDescriptor, prot);
133         return errno == 0;
134     }
135 
136     /**
137      * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory
138      * instance retains ownership of the FileDescriptor.
139      *
140      * This FileDescriptor is interoperable with the ASharedMemory NDK APIs.
141      *
142      * @return Returns the FileDescriptor associated with this object.
143      *
144      * @hide Exists only for MemoryFile interop
145      */
getFileDescriptor()146     public @NonNull FileDescriptor getFileDescriptor() {
147         return mFileDescriptor;
148     }
149 
150     /**
151      * Returns the backing native fd int for this SharedMemory object. The SharedMemory
152      * instance retains ownership of the fd.
153      *
154      * This fd is interoperable with the ASharedMemory NDK APIs.
155      *
156      * @return Returns the native fd associated with this object, or -1 if it is already closed.
157      *
158      * @hide Exposed for native ASharedMemory_dupFromJava()
159      */
160     @UnsupportedAppUsage
getFd()161     public int getFd() {
162         return mFileDescriptor.getInt$();
163     }
164 
165     /**
166      * @return The size of the SharedMemory region.
167      */
getSize()168     public int getSize() {
169         checkOpen();
170         return mSize;
171     }
172 
173     /**
174      * Creates a read/write mapping of the entire shared memory region. This requires the the
175      * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail.
176      *
177      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
178      * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize())
179      *
180      * @return A ByteBuffer mapping
181      * @throws ErrnoException if the mmap call failed.
182      */
mapReadWrite()183     public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
184         return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
185     }
186 
187     /**
188      * Creates a read-only mapping of the entire shared memory region. This requires the the
189      * protection level of the shared memory is at least PROT_READ or the map will fail.
190      *
191      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
192      * This is equivalent to map(OsConstants.PROT_READ, 0, getSize())
193      *
194      * @return A ByteBuffer mapping
195      * @throws ErrnoException if the mmap call failed.
196      */
mapReadOnly()197     public @NonNull ByteBuffer mapReadOnly() throws ErrnoException {
198         return map(OsConstants.PROT_READ, 0, mSize);
199     }
200 
201     /**
202      * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
203      * always produce a new ByteBuffer window to the backing shared memory region. Every call
204      * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
205      * returned by map() is no longer needed.
206      *
207      * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
208      * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
209      *         getSize().
210      * @param length The length of the region to map. Must be > 0 and offset + length must not
211      *         exceed getSize().
212      * @return A ByteBuffer mapping.
213      * @throws ErrnoException if the mmap call failed.
214      */
map(int prot, int offset, int length)215     public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
216         checkOpen();
217         validateProt(prot);
218         if (offset < 0) {
219             throw new IllegalArgumentException("Offset must be >= 0");
220         }
221         if (length <= 0) {
222             throw new IllegalArgumentException("Length must be > 0");
223         }
224         if (offset + length > mSize) {
225             throw new IllegalArgumentException("offset + length must not exceed getSize()");
226         }
227         long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
228         boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
229         Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
230         return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
231     }
232 
233     /**
234      * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
235      * release the backing memory of the ByteBuffer, invalidating all references to it. Only
236      * call this method if there are no duplicates of the ByteBuffer in use and don't
237      * access the ByteBuffer after calling this method.
238      *
239      * @param buffer The buffer to unmap
240      */
unmap(@onNull ByteBuffer buffer)241     public static void unmap(@NonNull ByteBuffer buffer) {
242         if (buffer instanceof DirectByteBuffer) {
243             NioUtils.freeDirectBuffer(buffer);
244         } else {
245             throw new IllegalArgumentException(
246                     "ByteBuffer wasn't created by #map(int, int, int); can't unmap");
247         }
248     }
249 
250     /**
251      * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all
252      * open mappings of the shared memory will remain valid and may continue to be used. The
253      * shared memory will not be freed until all file descriptor handles are closed and all
254      * memory mappings are unmapped.
255      */
256     @Override
close()257     public void close() {
258         if (mCleaner != null) {
259             mCleaner.clean();
260             mCleaner = null;
261         }
262     }
263 
264     @Override
describeContents()265     public int describeContents() {
266         return CONTENTS_FILE_DESCRIPTOR;
267     }
268 
269     @Override
writeToParcel(@onNull Parcel dest, int flags)270     public void writeToParcel(@NonNull Parcel dest, int flags) {
271         checkOpen();
272         dest.writeFileDescriptor(mFileDescriptor);
273     }
274 
275     public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
276             new Parcelable.Creator<SharedMemory>() {
277         @Override
278         public SharedMemory createFromParcel(Parcel source) {
279             FileDescriptor descriptor = source.readRawFileDescriptor();
280             return new SharedMemory(descriptor);
281         }
282 
283         @Override
284         public SharedMemory[] newArray(int size) {
285             return new SharedMemory[size];
286         }
287     };
288 
289     /**
290      * Cleaner that closes the FD
291      */
292     private static final class Closer implements Runnable {
293         private FileDescriptor mFd;
294         private MemoryRegistration mMemoryReference;
295 
Closer(FileDescriptor fd, MemoryRegistration memoryReference)296         private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
297             mFd = fd;
298             mMemoryReference = memoryReference;
299         }
300 
301         @Override
run()302         public void run() {
303             try {
304                 Os.close(mFd);
305             } catch (ErrnoException e) { /* swallow error */ }
306             mMemoryReference.release();
307             mMemoryReference = null;
308         }
309     }
310 
311     /**
312      * Cleaner that munmap regions
313      */
314     private static final class Unmapper implements Runnable {
315         private long mAddress;
316         private int mSize;
317         private MemoryRegistration mMemoryReference;
318 
Unmapper(long address, int size, MemoryRegistration memoryReference)319         private Unmapper(long address, int size, MemoryRegistration memoryReference) {
320             mAddress = address;
321             mSize = size;
322             mMemoryReference = memoryReference;
323         }
324 
325         @Override
run()326         public void run() {
327             try {
328                 Os.munmap(mAddress, mSize);
329             } catch (ErrnoException e) { /* swallow exception */ }
330             mMemoryReference.release();
331             mMemoryReference = null;
332         }
333     }
334 
335     /**
336      * Helper class that ensures that the native allocation pressure against the VM heap stays
337      * active until the FD is closed as well as all mappings from that FD are closed.
338      */
339     private static final class MemoryRegistration {
340         private int mSize;
341         private int mReferenceCount;
342 
MemoryRegistration(int size)343         private MemoryRegistration(int size) {
344             mSize = size;
345             mReferenceCount = 1;
346             VMRuntime.getRuntime().registerNativeAllocation(mSize);
347         }
348 
acquire()349         public synchronized MemoryRegistration acquire() {
350             mReferenceCount++;
351             return this;
352         }
353 
release()354         public synchronized void release() {
355             mReferenceCount--;
356             if (mReferenceCount == 0) {
357                 VMRuntime.getRuntime().registerNativeFree(mSize);
358             }
359         }
360     }
361 
nCreate(String name, int size)362     private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
nGetSize(FileDescriptor fd)363     private static native int nGetSize(FileDescriptor fd);
nSetProt(FileDescriptor fd, int prot)364     private static native int nSetProt(FileDescriptor fd, int prot);
365 }
366