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