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