Loading services/core/java/com/android/server/PinnerService.java +349 −115 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading Loading @@ -47,10 +49,18 @@ import dalvik.system.DexFile; import dalvik.system.VMRuntime; import java.io.FileDescriptor; import java.io.Closeable; import java.io.InputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.EOFException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.zip.ZipFile; import java.util.zip.ZipException; import java.util.zip.ZipEntry; /** * <p>PinnerService pins important files for key processes in memory.</p> * <p>Files to pin are specified in the config_defaultPinnerServiceFiles Loading @@ -60,16 +70,18 @@ import java.util.ArrayList; public final class PinnerService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "PinnerService"; private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max private static final String PIN_META_FILENAME = "pinlist.meta"; private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); private final Context mContext; private final boolean mShouldPinCamera; /* These lists protected by PinnerService monitor lock */ private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>(); private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>(); private final boolean mShouldPinCamera; private BinderService mBinderService; private final long MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max private PinnerHandler mPinnerHandler = null; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { Loading Loading @@ -146,18 +158,18 @@ public final class PinnerService extends SystemService { // Files to pin come from the overlay and can be specified per-device config String[] filesToPin = mContext.getResources().getStringArray( com.android.internal.R.array.config_defaultPinnerServiceFiles); // Continue trying to pin each file even if we fail to pin some of them for (String fileToPin : filesToPin) { PinnedFile pf = pinFile(fileToPin, Integer.MAX_VALUE, /*attemptPinIntrospection=*/false); if (pf == null) { Slog.e(TAG, "Failed to pin file = " + fileToPin); continue; } synchronized (this) { // Continue trying to pin remaining files even if there is a failure for (int i = 0; i < filesToPin.length; i++){ PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0); if (pf != null) { mPinnedFiles.add(pf); if (DEBUG) { Slog.i(TAG, "Pinned file = " + pf.mFilename); } } else { Slog.e(TAG, "Failed to pin file = " + filesToPin[i]); } } } } Loading @@ -166,45 +178,24 @@ public final class PinnerService extends SystemService { * Handler for camera pinning message */ private void handlePinCamera(int userHandle) { if (mShouldPinCamera) { synchronized(this) { boolean success = pinCamera(userHandle); if (!success) { //this is not necessarily an error if (!mShouldPinCamera) return; if (!pinCamera(userHandle)) { if (DEBUG) { Slog.v(TAG, "Failed to pin camera."); } } } } } /** * determine if the camera app is already pinned by comparing the * intent resolution to the pinned files list */ private boolean alreadyPinned(int userHandle) { ApplicationInfo cameraInfo = getCameraInfo(userHandle); if (cameraInfo == null ) { return false; } for (int i = 0; i < mPinnedCameraFiles.size(); i++) { if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) { if (DEBUG) { Slog.v(TAG, "Camera is already pinned"); } return true; } } return false; } private void unpinCameraApp() { for (int i = 0; i < mPinnedCameraFiles.size(); i++) { unpinFile(mPinnedCameraFiles.get(i)); } ArrayList<PinnedFile> pinnedCameraFiles; synchronized (this) { pinnedCameraFiles = new ArrayList<>(mPinnedCameraFiles); mPinnedCameraFiles.clear(); } for (PinnedFile pinnedFile : pinnedCameraFiles) { pinnedFile.close(); } } private boolean isResolverActivity(ActivityInfo info) { return ResolverActivity.class.getName().equals(info.name); Loading Loading @@ -255,15 +246,19 @@ public final class PinnerService extends SystemService { //pin APK String camAPK = cameraInfo.sourceDir; PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE); PinnedFile pf = pinFile(camAPK, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/true); if (pf == null) { Slog.e(TAG, "Failed to pin " + camAPK); return false; } if (DEBUG) { Slog.i(TAG, "Pinned " + pf.mFilename); Slog.i(TAG, "Pinned " + pf.fileName); } synchronized (this) { mPinnedCameraFiles.add(pf); } // determine the ABI from either ApplicationInfo or Build String arch = "arm"; Loading @@ -289,11 +284,13 @@ public final class PinnerService extends SystemService { //not pinning the oat/odex is not a fatal error for (String file : files) { pf = pinFile(file, 0, 0, MAX_CAMERA_PIN_SIZE); pf = pinFile(file, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/false); if (pf != null) { synchronized (this) { mPinnedCameraFiles.add(pf); } if (DEBUG) { Slog.i(TAG, "Pinned " + pf.mFilename); Slog.i(TAG, "Pinned " + pf.fileName); } } } Loading @@ -302,70 +299,294 @@ public final class PinnerService extends SystemService { } /** mlock length bytes of fileToPin in memory, starting at offset * length == 0 means pin from offset to end of file * maxSize == 0 means infinite /** mlock length bytes of fileToPin in memory * * If attemptPinIntrospection is true, then treat the file to pin as a zip file and * look for a "pinlist.meta" file in the archive root directory. The structure of this * file is a PINLIST_META as described below: * * <pre> * PINLIST_META: PIN_RANGE* * PIN_RANGE: PIN_START PIN_LENGTH * PIN_START: big endian i32: offset in bytes of pin region from file start * PIN_LENGTH: big endian i32: length of pin region in bytes * </pre> * * (We use big endian because that's what DataInputStream is hardcoded to use.) * * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0, * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file. * * After we open a file, we march through the list of pin ranges and attempt to pin * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs * before others, file generators can express pins in priority order, making most * effective use of the pinned-page quota. * * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a * meaningful interpretation. Also, a range locking a single byte of a page locks the * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries * are legal, but each pin of a byte counts toward the pin quota regardless of whether * that byte has already been pinned, so the generator of PINLIST_META ought to ensure * that ranges are non-overlapping. * * @param fileToPin Path to file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param attemptPinIntrospection If true, try to open file as a * zip in order to extract the * @return Pinned memory resource owner thing or null on error */ private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) { FileDescriptor fd = new FileDescriptor(); private static PinnedFile pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) { ZipFile fileAsZip = null; InputStream pinRangeStream = null; try { fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY); if (attemptPinIntrospection) { fileAsZip = maybeOpenZip(fileToPin); } StructStat sb = Os.fstat(fd); if (fileAsZip != null) { pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); } if (offset + length > sb.st_size) { Os.close(fd); Slog.e(TAG, "Failed to pin file " + fileToPin + ", request extends beyond end of file. offset + length = " + (offset + length) + ", file length = " + sb.st_size); return null; Slog.d(TAG, "pinRangeStream: " + pinRangeStream); PinRangeSource pinRangeSource = (pinRangeStream != null) ? new PinRangeSourceStream(pinRangeStream) : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); } finally { safeClose(pinRangeStream); safeClose(fileAsZip); // Also closes any streams we've opened } } if (length == 0) { length = sb.st_size - offset; /** * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the * error, and return null. */ private static ZipFile maybeOpenZip(String fileName) { ZipFile zip = null; try { zip = new ZipFile(fileName); } catch (IOException ex) { Slog.w(TAG, String.format( "could not open \"%s\" as zip: pinning as blob", fileName), ex); } return zip; } if (maxSize > 0 && length > maxSize) { Slog.e(TAG, "Could not pin file " + fileToPin + ", size = " + length + ", maxSize = " + maxSize); Os.close(fd); return null; /** * Open a pin metadata file in the zip if one is present. * * @param zipFile Zip file to search * @return Open input stream or null on any error */ private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) { ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); InputStream pinMetaStream = null; if (pinMetaEntry != null) { try { pinMetaStream = zipFile.getInputStream(pinMetaEntry); } catch (IOException ex) { Slog.w(TAG, String.format("error reading pin metadata \"%s\": pinning as blob", fileName), ex); } } return pinMetaStream; } long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset); Os.close(fd); private static abstract class PinRangeSource { /** Retrive a range to pin. * * @param outPinRange Receives the pin region * @return True if we filled in outPinRange or false if we're out of pin entries */ abstract boolean read(PinRange outPinRange); } Os.mlock(address, length); private static final class PinRangeSourceStatic extends PinRangeSource { private final int mPinStart; private final int mPinLength; private boolean mDone = false; return new PinnedFile(address, length, fileToPin); } catch (ErrnoException e) { Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage()); if(fd.valid()) { PinRangeSourceStatic(int pinStart, int pinLength) { mPinStart = pinStart; mPinLength = pinLength; } @Override boolean read(PinRange outPinRange) { outPinRange.start = mPinStart; outPinRange.length = mPinLength; boolean done = mDone; mDone = true; return !done; } } private static final class PinRangeSourceStream extends PinRangeSource { private final DataInputStream mStream; private boolean mDone = false; PinRangeSourceStream(InputStream stream) { mStream = new DataInputStream(stream); } @Override boolean read(PinRange outPinRange) { if (!mDone) { try { Os.close(fd); outPinRange.start = mStream.readInt(); outPinRange.length = mStream.readInt(); } catch (IOException ex) { mDone = true; } } catch (ErrnoException eClose) { Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage()); return !mDone; } } /** * Helper for pinFile. * * @param fileToPin Name of file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes * to pin. * @return PinnedFile or null on error */ private static PinnedFile pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource) { FileDescriptor fd = new FileDescriptor(); long address = -1; int mapSize = 0; try { int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW); fd = Os.open(fileToPin, openFlags, 0); mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE); address = Os.mmap(0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0); PinRange pinRange = new PinRange(); int bytesPinned = 0; // We pin at page granularity, so make sure the limit is page-aligned if (maxBytesToPin % PAGE_SIZE != 0) { maxBytesToPin -= maxBytesToPin % PAGE_SIZE; } while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) { int pinStart = pinRange.start; int pinLength = pinRange.length; pinStart = clamp(0, pinStart, mapSize); pinLength = clamp(0, pinLength, mapSize - pinStart); pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength); // mlock doesn't require the region to be page-aligned, but we snap the // lock region to page boundaries anyway so that we don't under-count // locking a single byte of a page as a charge of one byte even though the // OS will retain the whole page. Thanks to this adjustment, we slightly // over-count the pin charge of back-to-back pins touching the same page, // but better that than undercounting. Besides: nothing stops pin metafile // creators from making the actual regions page-aligned. pinLength += pinStart % PAGE_SIZE; pinStart -= pinStart % PAGE_SIZE; if (pinLength % PAGE_SIZE != 0) { pinLength += PAGE_SIZE - pinLength % PAGE_SIZE; } pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned); if (pinLength > 0) { if (DEBUG) { Slog.d(TAG, String.format( "pinning at %s %s bytes of %s", pinStart, pinLength, fileToPin)); } Os.mlock(address + pinStart, pinLength); } bytesPinned += pinLength; } PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned); address = -1; // Ownership transferred return pinnedFile; } catch (ErrnoException ex) { Slog.e(TAG, "Could not pin file " + fileToPin, ex); return null; } finally { safeClose(fd); if (address >= 0) { safeMunmap(address, mapSize); } } } private static boolean unpinFile(PinnedFile pf) { private static int clamp(int min, int value, int max) { return Math.max(min, Math.min(value, max)); } private static void safeMunmap(long address, long mapSize) { try { Os.munlock(pf.mAddress, pf.mLength); } catch (ErrnoException e) { Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage()); return false; Os.munmap(address, mapSize); } catch (ErrnoException ex) { Slog.w(TAG, "ignoring error in unmap", ex); } if (DEBUG) { Slog.i(TAG, "Unpinned file " + pf.mFilename ); } return true; /** * Close FD, swallowing irrelevant errors. */ private static void safeClose(@Nullable FileDescriptor fd) { if (fd != null && fd.valid()) { try { Os.close(fd); } catch (ErrnoException ex) { // Swallow the exception: non-EBADF errors in close(2) // indicate deferred paging write errors, which we // don't care about here. The underlying file // descriptor is always closed. if (ex.errno == OsConstants.EBADF) { throw new AssertionError(ex); } } } } /** * Close closeable thing, swallowing errors. */ private static void safeClose(@Nullable Closeable thing) { if (thing != null) { try { thing.close(); } catch (IOException ex) { Slog.w(TAG, "ignoring error closing resource: " + thing, ex); } } } private synchronized ArrayList<PinnedFile> snapshotPinnedFiles() { int nrPinnedFiles = mPinnedFiles.size() + mPinnedCameraFiles.size(); ArrayList<PinnedFile> pinnedFiles = new ArrayList<>(nrPinnedFiles); pinnedFiles.addAll(mPinnedFiles); pinnedFiles.addAll(mPinnedCameraFiles); return pinnedFiles; } private final class BinderService extends Binder { Loading @@ -373,33 +594,46 @@ public final class PinnerService extends SystemService { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; long totalSize = 0; pw.println("Pinned Files:"); synchronized(this) { for (int i = 0; i < mPinnedFiles.size(); i++) { pw.println(mPinnedFiles.get(i).mFilename); totalSize += mPinnedFiles.get(i).mLength; } for (int i = 0; i < mPinnedCameraFiles.size(); i++) { pw.println(mPinnedCameraFiles.get(i).mFilename); totalSize += mPinnedCameraFiles.get(i).mLength; for (PinnedFile pinnedFile : snapshotPinnedFiles()) { pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); totalSize += pinnedFile.bytesPinned; } } pw.println("Total size: " + totalSize); pw.format("Total size: %s\n", totalSize); } } private static class PinnedFile { long mAddress; long mLength; String mFilename; private static final class PinnedFile implements AutoCloseable { private long mAddress; final int mapSize; final String fileName; final int bytesPinned; PinnedFile(long address, long length, String filename) { PinnedFile(long address, int mapSize, String fileName, int bytesPinned) { mAddress = address; mLength = length; mFilename = filename; this.mapSize = mapSize; this.fileName = fileName; this.bytesPinned = bytesPinned; } @Override public void close() { if (mAddress >= 0) { safeMunmap(mAddress, mapSize); mAddress = -1; } } @Override public void finalize() { close(); } } final static class PinRange { int start; int length; } final class PinnerHandler extends Handler { static final int PIN_CAMERA_MSG = 4000; static final int PIN_ONSTART_MSG = 4001; Loading Loading
services/core/java/com/android/server/PinnerService.java +349 −115 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading Loading @@ -47,10 +49,18 @@ import dalvik.system.DexFile; import dalvik.system.VMRuntime; import java.io.FileDescriptor; import java.io.Closeable; import java.io.InputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.EOFException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.zip.ZipFile; import java.util.zip.ZipException; import java.util.zip.ZipEntry; /** * <p>PinnerService pins important files for key processes in memory.</p> * <p>Files to pin are specified in the config_defaultPinnerServiceFiles Loading @@ -60,16 +70,18 @@ import java.util.ArrayList; public final class PinnerService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "PinnerService"; private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max private static final String PIN_META_FILENAME = "pinlist.meta"; private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); private final Context mContext; private final boolean mShouldPinCamera; /* These lists protected by PinnerService monitor lock */ private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>(); private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>(); private final boolean mShouldPinCamera; private BinderService mBinderService; private final long MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max private PinnerHandler mPinnerHandler = null; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { Loading Loading @@ -146,18 +158,18 @@ public final class PinnerService extends SystemService { // Files to pin come from the overlay and can be specified per-device config String[] filesToPin = mContext.getResources().getStringArray( com.android.internal.R.array.config_defaultPinnerServiceFiles); // Continue trying to pin each file even if we fail to pin some of them for (String fileToPin : filesToPin) { PinnedFile pf = pinFile(fileToPin, Integer.MAX_VALUE, /*attemptPinIntrospection=*/false); if (pf == null) { Slog.e(TAG, "Failed to pin file = " + fileToPin); continue; } synchronized (this) { // Continue trying to pin remaining files even if there is a failure for (int i = 0; i < filesToPin.length; i++){ PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0); if (pf != null) { mPinnedFiles.add(pf); if (DEBUG) { Slog.i(TAG, "Pinned file = " + pf.mFilename); } } else { Slog.e(TAG, "Failed to pin file = " + filesToPin[i]); } } } } Loading @@ -166,45 +178,24 @@ public final class PinnerService extends SystemService { * Handler for camera pinning message */ private void handlePinCamera(int userHandle) { if (mShouldPinCamera) { synchronized(this) { boolean success = pinCamera(userHandle); if (!success) { //this is not necessarily an error if (!mShouldPinCamera) return; if (!pinCamera(userHandle)) { if (DEBUG) { Slog.v(TAG, "Failed to pin camera."); } } } } } /** * determine if the camera app is already pinned by comparing the * intent resolution to the pinned files list */ private boolean alreadyPinned(int userHandle) { ApplicationInfo cameraInfo = getCameraInfo(userHandle); if (cameraInfo == null ) { return false; } for (int i = 0; i < mPinnedCameraFiles.size(); i++) { if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) { if (DEBUG) { Slog.v(TAG, "Camera is already pinned"); } return true; } } return false; } private void unpinCameraApp() { for (int i = 0; i < mPinnedCameraFiles.size(); i++) { unpinFile(mPinnedCameraFiles.get(i)); } ArrayList<PinnedFile> pinnedCameraFiles; synchronized (this) { pinnedCameraFiles = new ArrayList<>(mPinnedCameraFiles); mPinnedCameraFiles.clear(); } for (PinnedFile pinnedFile : pinnedCameraFiles) { pinnedFile.close(); } } private boolean isResolverActivity(ActivityInfo info) { return ResolverActivity.class.getName().equals(info.name); Loading Loading @@ -255,15 +246,19 @@ public final class PinnerService extends SystemService { //pin APK String camAPK = cameraInfo.sourceDir; PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE); PinnedFile pf = pinFile(camAPK, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/true); if (pf == null) { Slog.e(TAG, "Failed to pin " + camAPK); return false; } if (DEBUG) { Slog.i(TAG, "Pinned " + pf.mFilename); Slog.i(TAG, "Pinned " + pf.fileName); } synchronized (this) { mPinnedCameraFiles.add(pf); } // determine the ABI from either ApplicationInfo or Build String arch = "arm"; Loading @@ -289,11 +284,13 @@ public final class PinnerService extends SystemService { //not pinning the oat/odex is not a fatal error for (String file : files) { pf = pinFile(file, 0, 0, MAX_CAMERA_PIN_SIZE); pf = pinFile(file, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/false); if (pf != null) { synchronized (this) { mPinnedCameraFiles.add(pf); } if (DEBUG) { Slog.i(TAG, "Pinned " + pf.mFilename); Slog.i(TAG, "Pinned " + pf.fileName); } } } Loading @@ -302,70 +299,294 @@ public final class PinnerService extends SystemService { } /** mlock length bytes of fileToPin in memory, starting at offset * length == 0 means pin from offset to end of file * maxSize == 0 means infinite /** mlock length bytes of fileToPin in memory * * If attemptPinIntrospection is true, then treat the file to pin as a zip file and * look for a "pinlist.meta" file in the archive root directory. The structure of this * file is a PINLIST_META as described below: * * <pre> * PINLIST_META: PIN_RANGE* * PIN_RANGE: PIN_START PIN_LENGTH * PIN_START: big endian i32: offset in bytes of pin region from file start * PIN_LENGTH: big endian i32: length of pin region in bytes * </pre> * * (We use big endian because that's what DataInputStream is hardcoded to use.) * * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0, * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file. * * After we open a file, we march through the list of pin ranges and attempt to pin * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs * before others, file generators can express pins in priority order, making most * effective use of the pinned-page quota. * * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a * meaningful interpretation. Also, a range locking a single byte of a page locks the * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries * are legal, but each pin of a byte counts toward the pin quota regardless of whether * that byte has already been pinned, so the generator of PINLIST_META ought to ensure * that ranges are non-overlapping. * * @param fileToPin Path to file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param attemptPinIntrospection If true, try to open file as a * zip in order to extract the * @return Pinned memory resource owner thing or null on error */ private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) { FileDescriptor fd = new FileDescriptor(); private static PinnedFile pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) { ZipFile fileAsZip = null; InputStream pinRangeStream = null; try { fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY); if (attemptPinIntrospection) { fileAsZip = maybeOpenZip(fileToPin); } StructStat sb = Os.fstat(fd); if (fileAsZip != null) { pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); } if (offset + length > sb.st_size) { Os.close(fd); Slog.e(TAG, "Failed to pin file " + fileToPin + ", request extends beyond end of file. offset + length = " + (offset + length) + ", file length = " + sb.st_size); return null; Slog.d(TAG, "pinRangeStream: " + pinRangeStream); PinRangeSource pinRangeSource = (pinRangeStream != null) ? new PinRangeSourceStream(pinRangeStream) : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); } finally { safeClose(pinRangeStream); safeClose(fileAsZip); // Also closes any streams we've opened } } if (length == 0) { length = sb.st_size - offset; /** * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the * error, and return null. */ private static ZipFile maybeOpenZip(String fileName) { ZipFile zip = null; try { zip = new ZipFile(fileName); } catch (IOException ex) { Slog.w(TAG, String.format( "could not open \"%s\" as zip: pinning as blob", fileName), ex); } return zip; } if (maxSize > 0 && length > maxSize) { Slog.e(TAG, "Could not pin file " + fileToPin + ", size = " + length + ", maxSize = " + maxSize); Os.close(fd); return null; /** * Open a pin metadata file in the zip if one is present. * * @param zipFile Zip file to search * @return Open input stream or null on any error */ private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) { ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); InputStream pinMetaStream = null; if (pinMetaEntry != null) { try { pinMetaStream = zipFile.getInputStream(pinMetaEntry); } catch (IOException ex) { Slog.w(TAG, String.format("error reading pin metadata \"%s\": pinning as blob", fileName), ex); } } return pinMetaStream; } long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset); Os.close(fd); private static abstract class PinRangeSource { /** Retrive a range to pin. * * @param outPinRange Receives the pin region * @return True if we filled in outPinRange or false if we're out of pin entries */ abstract boolean read(PinRange outPinRange); } Os.mlock(address, length); private static final class PinRangeSourceStatic extends PinRangeSource { private final int mPinStart; private final int mPinLength; private boolean mDone = false; return new PinnedFile(address, length, fileToPin); } catch (ErrnoException e) { Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage()); if(fd.valid()) { PinRangeSourceStatic(int pinStart, int pinLength) { mPinStart = pinStart; mPinLength = pinLength; } @Override boolean read(PinRange outPinRange) { outPinRange.start = mPinStart; outPinRange.length = mPinLength; boolean done = mDone; mDone = true; return !done; } } private static final class PinRangeSourceStream extends PinRangeSource { private final DataInputStream mStream; private boolean mDone = false; PinRangeSourceStream(InputStream stream) { mStream = new DataInputStream(stream); } @Override boolean read(PinRange outPinRange) { if (!mDone) { try { Os.close(fd); outPinRange.start = mStream.readInt(); outPinRange.length = mStream.readInt(); } catch (IOException ex) { mDone = true; } } catch (ErrnoException eClose) { Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage()); return !mDone; } } /** * Helper for pinFile. * * @param fileToPin Name of file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes * to pin. * @return PinnedFile or null on error */ private static PinnedFile pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource) { FileDescriptor fd = new FileDescriptor(); long address = -1; int mapSize = 0; try { int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW); fd = Os.open(fileToPin, openFlags, 0); mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE); address = Os.mmap(0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0); PinRange pinRange = new PinRange(); int bytesPinned = 0; // We pin at page granularity, so make sure the limit is page-aligned if (maxBytesToPin % PAGE_SIZE != 0) { maxBytesToPin -= maxBytesToPin % PAGE_SIZE; } while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) { int pinStart = pinRange.start; int pinLength = pinRange.length; pinStart = clamp(0, pinStart, mapSize); pinLength = clamp(0, pinLength, mapSize - pinStart); pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength); // mlock doesn't require the region to be page-aligned, but we snap the // lock region to page boundaries anyway so that we don't under-count // locking a single byte of a page as a charge of one byte even though the // OS will retain the whole page. Thanks to this adjustment, we slightly // over-count the pin charge of back-to-back pins touching the same page, // but better that than undercounting. Besides: nothing stops pin metafile // creators from making the actual regions page-aligned. pinLength += pinStart % PAGE_SIZE; pinStart -= pinStart % PAGE_SIZE; if (pinLength % PAGE_SIZE != 0) { pinLength += PAGE_SIZE - pinLength % PAGE_SIZE; } pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned); if (pinLength > 0) { if (DEBUG) { Slog.d(TAG, String.format( "pinning at %s %s bytes of %s", pinStart, pinLength, fileToPin)); } Os.mlock(address + pinStart, pinLength); } bytesPinned += pinLength; } PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned); address = -1; // Ownership transferred return pinnedFile; } catch (ErrnoException ex) { Slog.e(TAG, "Could not pin file " + fileToPin, ex); return null; } finally { safeClose(fd); if (address >= 0) { safeMunmap(address, mapSize); } } } private static boolean unpinFile(PinnedFile pf) { private static int clamp(int min, int value, int max) { return Math.max(min, Math.min(value, max)); } private static void safeMunmap(long address, long mapSize) { try { Os.munlock(pf.mAddress, pf.mLength); } catch (ErrnoException e) { Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage()); return false; Os.munmap(address, mapSize); } catch (ErrnoException ex) { Slog.w(TAG, "ignoring error in unmap", ex); } if (DEBUG) { Slog.i(TAG, "Unpinned file " + pf.mFilename ); } return true; /** * Close FD, swallowing irrelevant errors. */ private static void safeClose(@Nullable FileDescriptor fd) { if (fd != null && fd.valid()) { try { Os.close(fd); } catch (ErrnoException ex) { // Swallow the exception: non-EBADF errors in close(2) // indicate deferred paging write errors, which we // don't care about here. The underlying file // descriptor is always closed. if (ex.errno == OsConstants.EBADF) { throw new AssertionError(ex); } } } } /** * Close closeable thing, swallowing errors. */ private static void safeClose(@Nullable Closeable thing) { if (thing != null) { try { thing.close(); } catch (IOException ex) { Slog.w(TAG, "ignoring error closing resource: " + thing, ex); } } } private synchronized ArrayList<PinnedFile> snapshotPinnedFiles() { int nrPinnedFiles = mPinnedFiles.size() + mPinnedCameraFiles.size(); ArrayList<PinnedFile> pinnedFiles = new ArrayList<>(nrPinnedFiles); pinnedFiles.addAll(mPinnedFiles); pinnedFiles.addAll(mPinnedCameraFiles); return pinnedFiles; } private final class BinderService extends Binder { Loading @@ -373,33 +594,46 @@ public final class PinnerService extends SystemService { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; long totalSize = 0; pw.println("Pinned Files:"); synchronized(this) { for (int i = 0; i < mPinnedFiles.size(); i++) { pw.println(mPinnedFiles.get(i).mFilename); totalSize += mPinnedFiles.get(i).mLength; } for (int i = 0; i < mPinnedCameraFiles.size(); i++) { pw.println(mPinnedCameraFiles.get(i).mFilename); totalSize += mPinnedCameraFiles.get(i).mLength; for (PinnedFile pinnedFile : snapshotPinnedFiles()) { pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); totalSize += pinnedFile.bytesPinned; } } pw.println("Total size: " + totalSize); pw.format("Total size: %s\n", totalSize); } } private static class PinnedFile { long mAddress; long mLength; String mFilename; private static final class PinnedFile implements AutoCloseable { private long mAddress; final int mapSize; final String fileName; final int bytesPinned; PinnedFile(long address, long length, String filename) { PinnedFile(long address, int mapSize, String fileName, int bytesPinned) { mAddress = address; mLength = length; mFilename = filename; this.mapSize = mapSize; this.fileName = fileName; this.bytesPinned = bytesPinned; } @Override public void close() { if (mAddress >= 0) { safeMunmap(mAddress, mapSize); mAddress = -1; } } @Override public void finalize() { close(); } } final static class PinRange { int start; int length; } final class PinnerHandler extends Handler { static final int PIN_CAMERA_MSG = 4000; static final int PIN_ONSTART_MSG = 4001; Loading