1 /* 2 * Copyright (C) 2018 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.server.devicepolicy; 18 19 import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback; 20 import android.app.admin.StartInstallingUpdateCallback; 21 import android.content.Context; 22 import android.os.ParcelFileDescriptor; 23 import android.os.UpdateEngine; 24 import android.os.UpdateEngineCallback; 25 import android.util.Log; 26 27 import java.io.BufferedReader; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.nio.file.Paths; 31 import java.util.ArrayList; 32 import java.util.Enumeration; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.zip.ZipEntry; 37 import java.util.zip.ZipException; 38 import java.util.zip.ZipFile; 39 40 /** 41 * Used for installing an update on <a href="https://source.android.com/devices/tech/ota/ab">AB 42 * devices.</a> 43 * <p>This logic is specific to GOTA and should be modified by OEMs using a different AB update 44 * system.</p> 45 */ 46 class AbUpdateInstaller extends UpdateInstaller { 47 private static final String PAYLOAD_BIN = "payload.bin"; 48 private static final String PAYLOAD_PROPERTIES_TXT = "payload_properties.txt"; 49 //https://en.wikipedia.org/wiki/Zip_(file_format)#Local_file_header 50 private static final int OFFSET_TO_FILE_NAME = 30; 51 // kDownloadStateInitializationError constant from system/update_engine/common/error_code.h. 52 private static final int DOWNLOAD_STATE_INITIALIZATION_ERROR = 20; 53 private long mSizeForUpdate; 54 private long mOffsetForUpdate; 55 private List<String> mProperties; 56 private Enumeration<? extends ZipEntry> mEntries; 57 private ZipFile mPackedUpdateFile; 58 private static final Map<Integer, Integer> errorCodesMap = buildErrorCodesMap(); 59 private static final Map<Integer, String> errorStringsMap = buildErrorStringsMap(); 60 public static final String UNKNOWN_ERROR = "Unknown error with error code = "; 61 private boolean mUpdateInstalled; 62 buildErrorCodesMap()63 private static Map<Integer, Integer> buildErrorCodesMap() { 64 Map<Integer, Integer> map = new HashMap<>(); 65 map.put( 66 UpdateEngine.ErrorCodeConstants.ERROR, 67 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); 68 map.put( 69 DOWNLOAD_STATE_INITIALIZATION_ERROR, 70 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); 71 map.put( 72 UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR, 73 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); 74 75 // Error constants corresponding to errors related to bad update file. 76 map.put( 77 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR, 78 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); 79 map.put( 80 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR, 81 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); 82 map.put( 83 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR, 84 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); 85 map.put( 86 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, 87 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); 88 // TODO(b/133396459): replace with a constant. 89 map.put( 90 26 /* kDownloadMetadataSignatureMismatch */, 91 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); 92 93 // Error constants corresponding to errors related to devices bad state. 94 map.put( 95 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR, 96 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); 97 map.put( 98 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR, 99 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); 100 map.put( 101 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR, 102 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); 103 map.put( 104 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE, 105 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); 106 107 return map; 108 } 109 buildErrorStringsMap()110 private static Map<Integer, String> buildErrorStringsMap() { 111 Map<Integer, String> map = new HashMap<>(); 112 map.put(UpdateEngine.ErrorCodeConstants.ERROR, UNKNOWN_ERROR); 113 map.put( 114 DOWNLOAD_STATE_INITIALIZATION_ERROR, 115 "The delta update payload was targeted for another version or the source partition" 116 + "was modified after it was installed"); 117 map.put( 118 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR, 119 "Failed to finish the configured postinstall works."); 120 map.put( 121 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR, 122 "Failed to open one of the partitions it tried to write to or read data from."); 123 map.put( 124 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR, 125 "Payload mismatch error."); 126 map.put( 127 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR, 128 "Failed to read the payload data from the given URL."); 129 map.put( 130 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, "Payload hash error."); 131 map.put( 132 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR, 133 "Payload size mismatch error."); 134 map.put( 135 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR, 136 "Failed to verify the signature of the payload."); 137 map.put( 138 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE, 139 "The payload has been successfully installed," 140 + "but the active slot was not flipped."); 141 return map; 142 } 143 AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector, DevicePolicyConstants constants)144 AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor, 145 StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector, 146 DevicePolicyConstants constants) { 147 super(context, updateFileDescriptor, callback, injector, constants); 148 mUpdateInstalled = false; 149 } 150 151 @Override installUpdateInThread()152 public void installUpdateInThread() { 153 if (mUpdateInstalled) { 154 throw new IllegalStateException("installUpdateInThread can be called only once."); 155 } 156 try { 157 setState(); 158 applyPayload(Paths.get(mCopiedUpdateFile.getAbsolutePath()).toUri().toString()); 159 } catch (ZipException e) { 160 Log.w(UpdateInstaller.TAG, e); 161 notifyCallbackOnError( 162 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, 163 Log.getStackTraceString(e)); 164 } catch (IOException e) { 165 Log.w(UpdateInstaller.TAG, e); 166 notifyCallbackOnError( 167 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, 168 Log.getStackTraceString(e)); 169 } 170 } 171 setState()172 private void setState() throws IOException { 173 mUpdateInstalled = true; 174 mPackedUpdateFile = new ZipFile(mCopiedUpdateFile); 175 mProperties = new ArrayList<>(); 176 mSizeForUpdate = -1; 177 mOffsetForUpdate = 0; 178 mEntries = mPackedUpdateFile.entries(); 179 } 180 buildBoundUpdateEngine()181 private UpdateEngine buildBoundUpdateEngine() { 182 UpdateEngine updateEngine = new UpdateEngine(); 183 updateEngine.bind(new DelegatingUpdateEngineCallback(this, updateEngine)); 184 return updateEngine; 185 } 186 applyPayload(String updatePath)187 private void applyPayload(String updatePath) throws IOException { 188 if (!updateStateForPayload()) { 189 return; 190 } 191 String[] headerKeyValuePairs = mProperties.stream().toArray(String[]::new); 192 if (mSizeForUpdate == -1) { 193 Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package."); 194 notifyCallbackOnError( 195 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, 196 "Failed to find payload entry in the given package."); 197 return; 198 } 199 200 UpdateEngine updateEngine = buildBoundUpdateEngine(); 201 try { 202 updateEngine.applyPayload( 203 updatePath, mOffsetForUpdate, mSizeForUpdate, headerKeyValuePairs); 204 } catch (Exception e) { 205 // Prevent an automatic restart when an update is already being processed 206 // (http://b/124106342). 207 Log.w(UpdateInstaller.TAG, "Failed to install update from file.", e); 208 notifyCallbackOnError( 209 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, 210 "Failed to install update from file."); 211 } 212 } 213 updateStateForPayload()214 private boolean updateStateForPayload() throws IOException { 215 long offset = 0; 216 while (mEntries.hasMoreElements()) { 217 ZipEntry entry = mEntries.nextElement(); 218 219 String name = entry.getName(); 220 offset += buildOffsetForEntry(entry, name); 221 if (entry.isDirectory()) { 222 offset -= entry.getCompressedSize(); 223 continue; 224 } 225 if (PAYLOAD_BIN.equals(name)) { 226 if (entry.getMethod() != ZipEntry.STORED) { 227 Log.w(UpdateInstaller.TAG, "Invalid compression method."); 228 notifyCallbackOnError( 229 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, 230 "Invalid compression method."); 231 return false; 232 } 233 mSizeForUpdate = entry.getCompressedSize(); 234 mOffsetForUpdate = offset - entry.getCompressedSize(); 235 } else if (PAYLOAD_PROPERTIES_TXT.equals(name)) { 236 updatePropertiesForEntry(entry); 237 } 238 } 239 return true; 240 } 241 buildOffsetForEntry(ZipEntry entry, String name)242 private long buildOffsetForEntry(ZipEntry entry, String name) { 243 return OFFSET_TO_FILE_NAME + name.length() + entry.getCompressedSize() 244 + (entry.getExtra() == null ? 0 : entry.getExtra().length); 245 } 246 updatePropertiesForEntry(ZipEntry entry)247 private void updatePropertiesForEntry(ZipEntry entry) throws IOException { 248 try (BufferedReader bufferedReader = new BufferedReader( 249 new InputStreamReader(mPackedUpdateFile.getInputStream(entry)))) { 250 String line; 251 /* Neither @line nor @mProperties are size constraint since there is a few properties 252 with limited size. */ 253 while ((line = bufferedReader.readLine()) != null) { 254 mProperties.add(line); 255 } 256 } 257 } 258 259 private static class DelegatingUpdateEngineCallback extends UpdateEngineCallback { 260 private UpdateInstaller mUpdateInstaller; 261 private UpdateEngine mUpdateEngine; 262 DelegatingUpdateEngineCallback( UpdateInstaller updateInstaller, UpdateEngine updateEngine)263 DelegatingUpdateEngineCallback( 264 UpdateInstaller updateInstaller, UpdateEngine updateEngine) { 265 mUpdateInstaller = updateInstaller; 266 mUpdateEngine = updateEngine; 267 } 268 269 @Override onStatusUpdate(int statusCode, float percentage)270 public void onStatusUpdate(int statusCode, float percentage) { 271 return; 272 } 273 274 @Override onPayloadApplicationComplete(int errorCode)275 public void onPayloadApplicationComplete(int errorCode) { 276 mUpdateEngine.unbind(); 277 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) { 278 mUpdateInstaller.notifyCallbackOnSuccess(); 279 } else { 280 mUpdateInstaller.notifyCallbackOnError( 281 errorCodesMap.getOrDefault( 282 errorCode, InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN), 283 errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode)); 284 } 285 } 286 } 287 } 288