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.util.apk; 18 19 import android.system.ErrnoException; 20 import android.system.Os; 21 import android.system.OsConstants; 22 23 import java.io.FileDescriptor; 24 import java.io.IOException; 25 import java.nio.ByteBuffer; 26 import java.nio.DirectByteBuffer; 27 import java.security.DigestException; 28 29 /** 30 * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections 31 * of the file. 32 */ 33 class MemoryMappedFileDataSource implements DataSource { 34 private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE); 35 36 private final FileDescriptor mFd; 37 private final long mFilePosition; 38 private final long mSize; 39 40 /** 41 * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file. 42 * 43 * @param fd file descriptor to read from. 44 * @param position start position of the region in the file. 45 * @param size size (in bytes) of the region. 46 */ MemoryMappedFileDataSource(FileDescriptor fd, long position, long size)47 MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) { 48 mFd = fd; 49 mFilePosition = position; 50 mSize = size; 51 } 52 53 @Override size()54 public long size() { 55 return mSize; 56 } 57 58 @Override feedIntoDataDigester(DataDigester md, long offset, int size)59 public void feedIntoDataDigester(DataDigester md, long offset, int size) 60 throws IOException, DigestException { 61 // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this 62 // method was settled on a straightforward mmap with prefaulting. 63 // 64 // This method is not using FileChannel.map API because that API does not offset a way 65 // to "prefault" the resulting memory pages. Without prefaulting, performance is about 66 // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB 67 // range. FileChannel.load (which currently uses madvise) doesn't help. Finally, 68 // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of 69 // time, which is not compensated for by faster reads. 70 71 // We mmap the smallest region of the file containing the requested data. mmap requires 72 // that the start offset in the file must be a multiple of memory page size. We thus may 73 // need to mmap from an offset less than the requested offset. 74 long filePosition = mFilePosition + offset; 75 long mmapFilePosition = 76 (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES; 77 int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition); 78 long mmapRegionSize = size + dataStartOffsetInMmapRegion; 79 long mmapPtr = 0; 80 try { 81 mmapPtr = Os.mmap( 82 0, // let the OS choose the start address of the region in memory 83 mmapRegionSize, 84 OsConstants.PROT_READ, 85 OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages 86 mFd, 87 mmapFilePosition); 88 ByteBuffer buf = new DirectByteBuffer( 89 size, 90 mmapPtr + dataStartOffsetInMmapRegion, 91 mFd, // not really needed, but just in case 92 null, // no need to clean up -- it's taken care of by the finally block 93 true // read only buffer 94 ); 95 md.consume(buf); 96 } catch (ErrnoException e) { 97 throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e); 98 } finally { 99 if (mmapPtr != 0) { 100 try { 101 Os.munmap(mmapPtr, mmapRegionSize); 102 } catch (ErrnoException ignored) { } 103 } 104 } 105 } 106 } 107