1 /* 2 * Copyright (C) 2019 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 com.android.dynsystem; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.gsi.AvbPublicKey; 22 import android.gsi.IGsiService; 23 import android.net.Uri; 24 import android.os.AsyncTask; 25 import android.os.Build; 26 import android.os.SharedMemory; 27 import android.os.SystemProperties; 28 import android.os.image.DynamicSystemManager; 29 import android.service.persistentdata.PersistentDataBlockManager; 30 import android.system.ErrnoException; 31 import android.util.Log; 32 import android.util.Pair; 33 import android.util.Range; 34 import android.webkit.URLUtil; 35 36 import org.json.JSONException; 37 38 import java.io.BufferedInputStream; 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.net.URL; 43 import java.nio.ByteBuffer; 44 import java.util.Arrays; 45 import java.util.Enumeration; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.concurrent.ExecutionException; 49 import java.util.concurrent.ExecutorService; 50 import java.util.concurrent.Executors; 51 import java.util.concurrent.Future; 52 import java.util.zip.GZIPInputStream; 53 import java.util.zip.ZipEntry; 54 import java.util.zip.ZipFile; 55 import java.util.zip.ZipInputStream; 56 57 class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { 58 59 private static final String TAG = "InstallationAsyncTask"; 60 61 private static final int MIN_SHARED_MEMORY_SIZE = 8 << 10; // 8KiB 62 private static final int MAX_SHARED_MEMORY_SIZE = 8 << 20; // 8MiB 63 private static final int DEFAULT_SHARED_MEMORY_SIZE = 512 << 10; // 512KiB 64 private static final String SHARED_MEMORY_SIZE_PROP = 65 "dynamic_system.data_transfer.shared_memory.size"; 66 67 private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27; 68 69 private static final List<String> UNSUPPORTED_PARTITIONS = 70 Arrays.asList( 71 "vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other", "scratch"); 72 73 private class UnsupportedUrlException extends Exception { UnsupportedUrlException(String message)74 private UnsupportedUrlException(String message) { 75 super(message); 76 } 77 } 78 79 private class UnsupportedFormatException extends Exception { UnsupportedFormatException(String message)80 private UnsupportedFormatException(String message) { 81 super(message); 82 } 83 } 84 85 static class ImageValidationException extends Exception { ImageValidationException(String message)86 ImageValidationException(String message) { 87 super(message); 88 } 89 ImageValidationException(Throwable cause)90 ImageValidationException(Throwable cause) { 91 super(cause); 92 } 93 } 94 95 static class RevocationListFetchException extends ImageValidationException { RevocationListFetchException(Throwable cause)96 RevocationListFetchException(Throwable cause) { 97 super(cause); 98 } 99 } 100 101 static class KeyRevokedException extends ImageValidationException { KeyRevokedException(String message)102 KeyRevokedException(String message) { 103 super(message); 104 } 105 } 106 107 static class PublicKeyException extends ImageValidationException { PublicKeyException(String message)108 PublicKeyException(String message) { 109 super(message); 110 } 111 } 112 113 static class InsufficientSpaceException extends IOException { InsufficientSpaceException(String message)114 InsufficientSpaceException(String message) { 115 super(message); 116 } 117 } 118 119 /** UNSET means the installation is not completed */ 120 static final int RESULT_UNSET = 0; 121 122 static final int RESULT_OK = 1; 123 static final int RESULT_CANCELLED = 2; 124 static final int RESULT_ERROR_IO = 3; 125 static final int RESULT_ERROR_UNSUPPORTED_URL = 4; 126 static final int RESULT_ERROR_UNSUPPORTED_FORMAT = 5; 127 static final int RESULT_ERROR_EXCEPTION = 6; 128 129 static class Progress { 130 public final String partitionName; 131 public final long installedBytes; 132 public final long totalBytes; 133 public final int partitionNumber; 134 public final int totalPartitionNumber; 135 public final int totalProgressPercentage; 136 Progress( String partitionName, long installedBytes, long totalBytes, int partitionNumber, int totalPartitionNumber, int totalProgressPercentage)137 Progress( 138 String partitionName, 139 long installedBytes, 140 long totalBytes, 141 int partitionNumber, 142 int totalPartitionNumber, 143 int totalProgressPercentage) { 144 this.partitionName = partitionName; 145 this.installedBytes = installedBytes; 146 this.totalBytes = totalBytes; 147 this.partitionNumber = partitionNumber; 148 this.totalPartitionNumber = totalPartitionNumber; 149 this.totalProgressPercentage = totalProgressPercentage; 150 } 151 } 152 153 interface ProgressListener { onProgressUpdate(Progress progress)154 void onProgressUpdate(Progress progress); 155 onResult(int resultCode, Throwable detail)156 void onResult(int resultCode, Throwable detail); 157 } 158 159 private static class MappedMemoryBuffer implements AutoCloseable { 160 public ByteBuffer mBuffer; 161 MappedMemoryBuffer(@onNull ByteBuffer buffer)162 MappedMemoryBuffer(@NonNull ByteBuffer buffer) { 163 mBuffer = buffer; 164 } 165 166 @Override close()167 public void close() { 168 if (mBuffer != null) { 169 SharedMemory.unmap(mBuffer); 170 mBuffer = null; 171 } 172 } 173 } 174 175 private final int mSharedMemorySize; 176 private final String mUrl; 177 private final String mDsuSlot; 178 private final String mPublicKey; 179 private final long mSystemSize; 180 private final long mUserdataSize; 181 private final Context mContext; 182 private final DynamicSystemManager mDynSystem; 183 private final ProgressListener mListener; 184 private final boolean mIsNetworkUrl; 185 private final boolean mIsDeviceBootloaderUnlocked; 186 private final boolean mWantScratchPartition; 187 private int mCreatePartitionStatus; 188 private DynamicSystemManager.Session mInstallationSession; 189 private KeyRevocationList mKeyRevocationList; 190 191 private boolean mIsZip; 192 private boolean mIsCompleted; 193 private InputStream mStream; 194 private ZipFile mZipFile; 195 196 private static final double PROGRESS_READONLY_PARTITION_WEIGHT = 0.8; 197 private static final double PROGRESS_WRITABLE_PARTITION_WEIGHT = 0.2; 198 199 private String mProgressPartitionName; 200 private long mProgressTotalBytes; 201 private int mProgressPartitionNumber; 202 private boolean mProgressPartitionIsReadonly; 203 private int mProgressCompletedReadonlyPartitions; 204 private int mProgressCompletedWritablePartitions; 205 private int mTotalReadonlyPartitions; 206 private int mTotalWritablePartitions; 207 private int mTotalPartitionNumber; 208 InstallationAsyncTask( String url, String dsuSlot, String publicKey, long systemSize, long userdataSize, Context context, DynamicSystemManager dynSystem, ProgressListener listener)209 InstallationAsyncTask( 210 String url, 211 String dsuSlot, 212 String publicKey, 213 long systemSize, 214 long userdataSize, 215 Context context, 216 DynamicSystemManager dynSystem, 217 ProgressListener listener) { 218 mSharedMemorySize = 219 Range.create(MIN_SHARED_MEMORY_SIZE, MAX_SHARED_MEMORY_SIZE) 220 .clamp( 221 SystemProperties.getInt( 222 SHARED_MEMORY_SIZE_PROP, DEFAULT_SHARED_MEMORY_SIZE)); 223 mUrl = url; 224 mDsuSlot = dsuSlot; 225 mPublicKey = publicKey; 226 mSystemSize = systemSize; 227 mUserdataSize = userdataSize; 228 mContext = context; 229 mDynSystem = dynSystem; 230 mListener = listener; 231 mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); 232 PersistentDataBlockManager pdbManager = 233 (PersistentDataBlockManager) 234 mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 235 mIsDeviceBootloaderUnlocked = 236 (pdbManager != null) 237 && (pdbManager.getFlashLockState() 238 == PersistentDataBlockManager.FLASH_LOCK_UNLOCKED); 239 mWantScratchPartition = Build.IS_DEBUGGABLE; 240 } 241 242 @Override doInBackground(String... voids)243 protected Throwable doInBackground(String... voids) { 244 Log.d(TAG, "Start doInBackground(), URL: " + mUrl); 245 246 try { 247 // call DynamicSystemManager to cleanup stuff 248 mDynSystem.remove(); 249 250 verifyAndPrepare(); 251 252 mDynSystem.startInstallation(mDsuSlot); 253 254 installUserdata(); 255 if (isCancelled()) { 256 mDynSystem.remove(); 257 return null; 258 } 259 if (mUrl == null) { 260 mDynSystem.finishInstallation(); 261 return null; 262 } 263 installImages(); 264 if (isCancelled()) { 265 mDynSystem.remove(); 266 return null; 267 } 268 269 if (mWantScratchPartition) { 270 // If host is debuggable, then install a scratch partition so that we can do 271 // adb remount in the guest system. 272 try { 273 installScratch(); 274 } catch (IOException e) { 275 // Failing to install overlayFS scratch shouldn't be fatal. 276 // Just ignore the error and skip installing the scratch partition. 277 Log.w(TAG, e.toString(), e); 278 } 279 if (isCancelled()) { 280 mDynSystem.remove(); 281 return null; 282 } 283 } 284 285 mDynSystem.finishInstallation(); 286 } catch (Exception e) { 287 Log.e(TAG, e.toString(), e); 288 mDynSystem.remove(); 289 return e; 290 } finally { 291 close(); 292 } 293 294 return null; 295 } 296 297 @Override onPostExecute(Throwable detail)298 protected void onPostExecute(Throwable detail) { 299 int result = RESULT_UNSET; 300 301 if (detail == null) { 302 result = RESULT_OK; 303 mIsCompleted = true; 304 } else if (detail instanceof IOException) { 305 result = RESULT_ERROR_IO; 306 } else if (detail instanceof UnsupportedUrlException) { 307 result = RESULT_ERROR_UNSUPPORTED_URL; 308 } else if (detail instanceof UnsupportedFormatException) { 309 result = RESULT_ERROR_UNSUPPORTED_FORMAT; 310 } else { 311 result = RESULT_ERROR_EXCEPTION; 312 } 313 314 Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result); 315 316 mListener.onResult(result, detail); 317 } 318 319 @Override onCancelled()320 protected void onCancelled() { 321 Log.d(TAG, "onCancelled(), URL: " + mUrl); 322 323 if (mDynSystem.abort()) { 324 Log.d(TAG, "Installation aborted"); 325 } else { 326 Log.w(TAG, "DynamicSystemManager.abort() returned false"); 327 } 328 329 mListener.onResult(RESULT_CANCELLED, null); 330 } 331 332 @Override onProgressUpdate(Long... progress)333 protected void onProgressUpdate(Long... progress) { 334 final long installedBytes = progress[0]; 335 int totalProgressPercentage = 0; 336 if (mTotalPartitionNumber > 0) { 337 final double readonlyPartitionWeight = 338 mTotalReadonlyPartitions > 0 339 ? PROGRESS_READONLY_PARTITION_WEIGHT / mTotalReadonlyPartitions 340 : 0; 341 final double writablePartitionWeight = 342 mTotalWritablePartitions > 0 343 ? PROGRESS_WRITABLE_PARTITION_WEIGHT / mTotalWritablePartitions 344 : 0; 345 double totalProgress = 0.0; 346 if (mProgressTotalBytes > 0) { 347 totalProgress += 348 (mProgressPartitionIsReadonly 349 ? readonlyPartitionWeight 350 : writablePartitionWeight) 351 * installedBytes 352 / mProgressTotalBytes; 353 } 354 totalProgress += readonlyPartitionWeight * mProgressCompletedReadonlyPartitions; 355 totalProgress += writablePartitionWeight * mProgressCompletedWritablePartitions; 356 totalProgressPercentage = (int) (totalProgress * 100); 357 } 358 mListener.onProgressUpdate( 359 new Progress( 360 mProgressPartitionName, 361 installedBytes, 362 mProgressTotalBytes, 363 mProgressPartitionNumber, 364 mTotalPartitionNumber, 365 totalProgressPercentage)); 366 } 367 initPartitionProgress(String partitionName, long totalBytes, boolean readonly)368 private void initPartitionProgress(String partitionName, long totalBytes, boolean readonly) { 369 if (mProgressPartitionNumber > 0) { 370 // Assume previous partition completed successfully. 371 if (mProgressPartitionIsReadonly) { 372 ++mProgressCompletedReadonlyPartitions; 373 } else { 374 ++mProgressCompletedWritablePartitions; 375 } 376 } 377 mProgressPartitionName = partitionName; 378 mProgressTotalBytes = totalBytes; 379 mProgressPartitionIsReadonly = readonly; 380 ++mProgressPartitionNumber; 381 } 382 verifyAndPrepare()383 private void verifyAndPrepare() throws Exception { 384 if (mUrl == null) { 385 return; 386 } 387 String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1); 388 389 if ("gz".equals(extension) || "gzip".equals(extension)) { 390 mIsZip = false; 391 } else if ("zip".equals(extension)) { 392 mIsZip = true; 393 } else { 394 throw new UnsupportedFormatException( 395 String.format(Locale.US, "Unsupported file format: %s", mUrl)); 396 } 397 398 if (mIsNetworkUrl) { 399 mStream = new URL(mUrl).openStream(); 400 } else if (URLUtil.isFileUrl(mUrl)) { 401 if (mIsZip) { 402 mZipFile = new ZipFile(new File(new URL(mUrl).toURI())); 403 } else { 404 mStream = new URL(mUrl).openStream(); 405 } 406 } else if (URLUtil.isContentUrl(mUrl)) { 407 mStream = mContext.getContentResolver().openInputStream(Uri.parse(mUrl)); 408 } else { 409 throw new UnsupportedUrlException( 410 String.format(Locale.US, "Unsupported URL: %s", mUrl)); 411 } 412 413 boolean hasTotalPartitionNumber = false; 414 if (mIsZip) { 415 if (mZipFile != null) { 416 // {*.img in zip} + {userdata} 417 hasTotalPartitionNumber = true; 418 mTotalReadonlyPartitions = calculateNumberOfImagesInLocalZip(mZipFile); 419 mTotalWritablePartitions = 1; 420 } else { 421 // TODO: Come up with a way to retrieve the number of total partitions from 422 // network URL. 423 } 424 } else { 425 // gzip has exactly two partitions, {system, userdata} 426 hasTotalPartitionNumber = true; 427 mTotalReadonlyPartitions = 1; 428 mTotalWritablePartitions = 1; 429 } 430 431 if (hasTotalPartitionNumber) { 432 if (mWantScratchPartition) { 433 // {scratch} 434 ++mTotalWritablePartitions; 435 } 436 mTotalPartitionNumber = mTotalReadonlyPartitions + mTotalWritablePartitions; 437 } 438 439 try { 440 String listUrl = mContext.getString(R.string.key_revocation_list_url); 441 mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl)); 442 } catch (IOException | JSONException e) { 443 mKeyRevocationList = new KeyRevocationList(); 444 imageValidationThrowOrWarning(new RevocationListFetchException(e)); 445 } 446 if (mKeyRevocationList.isRevoked(mPublicKey)) { 447 imageValidationThrowOrWarning(new KeyRevokedException(mPublicKey)); 448 } 449 } 450 imageValidationThrowOrWarning(ImageValidationException e)451 private void imageValidationThrowOrWarning(ImageValidationException e) 452 throws ImageValidationException { 453 if (mIsDeviceBootloaderUnlocked || !mIsNetworkUrl) { 454 // If device is OEM unlocked or DSU is being installed from a local file URI, 455 // then be permissive. 456 Log.w(TAG, e.toString()); 457 } else { 458 throw e; 459 } 460 } 461 installWritablePartition(final String partitionName, final long partitionSize)462 private void installWritablePartition(final String partitionName, final long partitionSize) 463 throws IOException { 464 Log.d(TAG, "Creating writable partition: " + partitionName + ", size: " + partitionSize); 465 466 mCreatePartitionStatus = 0; 467 mInstallationSession = null; 468 Thread thread = 469 new Thread() { 470 @Override 471 public void run() { 472 Pair<Integer, DynamicSystemManager.Session> result = 473 mDynSystem.createPartition( 474 partitionName, partitionSize, /* readOnly = */ false); 475 mCreatePartitionStatus = result.first; 476 mInstallationSession = result.second; 477 } 478 }; 479 480 initPartitionProgress(partitionName, partitionSize, /* readonly = */ false); 481 publishProgress(/* installedSize = */ 0L); 482 483 long prevInstalledSize = 0; 484 thread.start(); 485 while (thread.isAlive()) { 486 if (isCancelled()) { 487 return; 488 } 489 490 final long installedSize = mDynSystem.getInstallationProgress().bytes_processed; 491 492 if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) { 493 publishProgress(installedSize); 494 prevInstalledSize = installedSize; 495 } 496 497 try { 498 Thread.sleep(100); 499 } catch (InterruptedException e) { 500 // Ignore the error. 501 } 502 } 503 504 if (mInstallationSession == null) { 505 if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE 506 || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) { 507 throw new InsufficientSpaceException( 508 "Failed to create " 509 + partitionName 510 + " partition: storage media has insufficient free space"); 511 } else { 512 throw new IOException( 513 "Failed to start installation with requested size: " + partitionSize); 514 } 515 } 516 517 // Reset installation session and verify that installation completes successfully. 518 mInstallationSession = null; 519 if (!mDynSystem.closePartition()) { 520 throw new IOException("Failed to complete partition installation: " + partitionName); 521 } 522 523 // Ensure a 100% mark is published. 524 if (prevInstalledSize != partitionSize) { 525 publishProgress(partitionSize); 526 } 527 } 528 installScratch()529 private void installScratch() throws IOException { 530 installWritablePartition("scratch", mDynSystem.suggestScratchSize()); 531 } 532 installUserdata()533 private void installUserdata() throws IOException { 534 installWritablePartition("userdata", mUserdataSize); 535 } 536 installImages()537 private void installImages() throws ExecutionException, IOException, ImageValidationException { 538 if (mStream != null) { 539 if (mIsZip) { 540 installStreamingZipUpdate(); 541 } else { 542 installStreamingGzUpdate(); 543 } 544 } else { 545 installLocalZipUpdate(); 546 } 547 } 548 installStreamingGzUpdate()549 private void installStreamingGzUpdate() 550 throws ExecutionException, IOException, ImageValidationException { 551 Log.d(TAG, "To install a streaming GZ update"); 552 installImage("system", mSystemSize, new GZIPInputStream(mStream)); 553 } 554 shouldInstallEntry(String name)555 private boolean shouldInstallEntry(String name) { 556 if (!name.endsWith(".img")) { 557 return false; 558 } 559 String partitionName = name.substring(0, name.length() - 4); 560 if (UNSUPPORTED_PARTITIONS.contains(partitionName)) { 561 return false; 562 } 563 return true; 564 } 565 calculateNumberOfImagesInLocalZip(ZipFile zipFile)566 private int calculateNumberOfImagesInLocalZip(ZipFile zipFile) { 567 int total = 0; 568 Enumeration<? extends ZipEntry> entries = zipFile.entries(); 569 while (entries.hasMoreElements()) { 570 ZipEntry entry = entries.nextElement(); 571 if (shouldInstallEntry(entry.getName())) { 572 ++total; 573 } 574 } 575 return total; 576 } 577 installStreamingZipUpdate()578 private void installStreamingZipUpdate() 579 throws ExecutionException, IOException, ImageValidationException { 580 Log.d(TAG, "To install a streaming ZIP update"); 581 582 ZipInputStream zis = new ZipInputStream(mStream); 583 ZipEntry entry = null; 584 585 while ((entry = zis.getNextEntry()) != null) { 586 String name = entry.getName(); 587 if (shouldInstallEntry(name)) { 588 installImageFromAnEntry(entry, zis); 589 } else { 590 Log.d(TAG, name + " installation is not supported, skip it."); 591 } 592 593 if (isCancelled()) { 594 break; 595 } 596 } 597 } 598 installLocalZipUpdate()599 private void installLocalZipUpdate() 600 throws ExecutionException, IOException, ImageValidationException { 601 Log.d(TAG, "To install a local ZIP update"); 602 603 Enumeration<? extends ZipEntry> entries = mZipFile.entries(); 604 605 while (entries.hasMoreElements()) { 606 ZipEntry entry = entries.nextElement(); 607 String name = entry.getName(); 608 if (shouldInstallEntry(name)) { 609 installImageFromAnEntry(entry, mZipFile.getInputStream(entry)); 610 } else { 611 Log.d(TAG, name + " installation is not supported, skip it."); 612 } 613 614 if (isCancelled()) { 615 break; 616 } 617 } 618 } 619 installImageFromAnEntry(ZipEntry entry, InputStream is)620 private void installImageFromAnEntry(ZipEntry entry, InputStream is) 621 throws ExecutionException, IOException, ImageValidationException { 622 String name = entry.getName(); 623 624 Log.d(TAG, "ZipEntry: " + name); 625 626 String partitionName = name.substring(0, name.length() - 4); 627 long uncompressedSize = entry.getSize(); 628 629 installImage(partitionName, uncompressedSize, is); 630 } 631 installImage(String partitionName, long uncompressedSize, InputStream is)632 private void installImage(String partitionName, long uncompressedSize, InputStream is) 633 throws ExecutionException, IOException, ImageValidationException { 634 635 SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is)); 636 637 long unsparseSize = sis.getUnsparseSize(); 638 639 final long partitionSize; 640 641 if (unsparseSize != -1) { 642 partitionSize = unsparseSize; 643 Log.d(TAG, partitionName + " is sparse, raw size = " + unsparseSize); 644 } else if (uncompressedSize != -1) { 645 partitionSize = uncompressedSize; 646 Log.d(TAG, partitionName + " is already unsparse, raw size = " + uncompressedSize); 647 } else { 648 throw new IOException("Cannot get raw size for " + partitionName); 649 } 650 651 mCreatePartitionStatus = 0; 652 mInstallationSession = null; 653 Thread thread = 654 new Thread() { 655 @Override 656 public void run() { 657 Pair<Integer, DynamicSystemManager.Session> result = 658 mDynSystem.createPartition( 659 partitionName, partitionSize, /* readOnly = */ true); 660 mCreatePartitionStatus = result.first; 661 mInstallationSession = result.second; 662 } 663 }; 664 665 Log.d(TAG, "Start creating partition: " + partitionName); 666 thread.start(); 667 668 while (thread.isAlive()) { 669 if (isCancelled()) { 670 return; 671 } 672 673 try { 674 Thread.sleep(100); 675 } catch (InterruptedException e) { 676 // Ignore the error. 677 } 678 } 679 680 if (mInstallationSession == null) { 681 if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE 682 || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) { 683 throw new InsufficientSpaceException( 684 "Failed to create " 685 + partitionName 686 + " partition: storage media has insufficient free space"); 687 } else { 688 throw new IOException( 689 "Failed to start installation with requested size: " + partitionSize); 690 } 691 } 692 693 Log.d(TAG, "Start installing: " + partitionName); 694 695 long prevInstalledSize = 0; 696 try (SharedMemory sharedMemory = 697 SharedMemory.create("dsu_buffer_" + partitionName, mSharedMemorySize); 698 MappedMemoryBuffer mappedBuffer = 699 new MappedMemoryBuffer(sharedMemory.mapReadWrite())) { 700 mInstallationSession.setAshmem(sharedMemory.getFdDup(), sharedMemory.getSize()); 701 702 initPartitionProgress(partitionName, partitionSize, /* readonly = */ true); 703 publishProgress(/* installedSize = */ 0L); 704 705 long installedSize = 0; 706 byte[] readBuffer = new byte[sharedMemory.getSize()]; 707 ByteBuffer buffer = mappedBuffer.mBuffer; 708 ExecutorService executor = Executors.newSingleThreadExecutor(); 709 Future<Boolean> submitPromise = null; 710 711 while (true) { 712 final int numBytesRead = sis.read(readBuffer, 0, readBuffer.length); 713 714 if (submitPromise != null) { 715 // Wait until the previous submit task is complete. 716 while (true) { 717 try { 718 if (!submitPromise.get()) { 719 throw new IOException("Failed submitFromAshmem() to DynamicSystem"); 720 } 721 break; 722 } catch (InterruptedException e) { 723 // Ignore. 724 } 725 } 726 727 // Publish the progress of the previous submit task. 728 if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) { 729 publishProgress(installedSize); 730 prevInstalledSize = installedSize; 731 } 732 } 733 734 // Ensure the previous submit task (submitPromise) is complete before exiting the 735 // loop. 736 if (numBytesRead < 0) { 737 break; 738 } 739 740 if (isCancelled()) { 741 return; 742 } 743 744 buffer.position(0); 745 buffer.put(readBuffer, 0, numBytesRead); 746 submitPromise = 747 executor.submit(() -> mInstallationSession.submitFromAshmem(numBytesRead)); 748 749 // Even though we update the bytes counter here, the actual progress is updated only 750 // after the submit task (submitPromise) is complete. 751 installedSize += numBytesRead; 752 } 753 } catch (ErrnoException e) { 754 e.rethrowAsIOException(); 755 } 756 757 AvbPublicKey avbPublicKey = new AvbPublicKey(); 758 if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) { 759 imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed")); 760 } else { 761 String publicKey = toHexString(avbPublicKey.sha1); 762 if (mKeyRevocationList.isRevoked(publicKey)) { 763 imageValidationThrowOrWarning(new KeyRevokedException(publicKey)); 764 } 765 } 766 767 // Reset installation session and verify that installation completes successfully. 768 mInstallationSession = null; 769 if (!mDynSystem.closePartition()) { 770 throw new IOException("Failed to complete partition installation: " + partitionName); 771 } 772 773 // Ensure a 100% mark is published. 774 if (prevInstalledSize != partitionSize) { 775 publishProgress(partitionSize); 776 } 777 } 778 toHexString(byte[] bytes)779 private static String toHexString(byte[] bytes) { 780 StringBuilder sb = new StringBuilder(); 781 for (byte b : bytes) { 782 sb.append(String.format("%02x", b)); 783 } 784 return sb.toString(); 785 } 786 close()787 private void close() { 788 try { 789 if (mStream != null) { 790 mStream.close(); 791 mStream = null; 792 } 793 if (mZipFile != null) { 794 mZipFile.close(); 795 mZipFile = null; 796 } 797 } catch (IOException e) { 798 // ignore 799 } 800 } 801 isCompleted()802 boolean isCompleted() { 803 return mIsCompleted; 804 } 805 commit(boolean oneShot)806 boolean commit(boolean oneShot) { 807 return mDynSystem.setEnable(true, oneShot); 808 } 809 } 810