blob: d5cfab9601711434120ca4f0fca75daa7c7332d3 [file] [log] [blame]
Neda Topoljanac19f29162018-10-22 18:12:16 +01001/*
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
17package com.android.server.devicepolicy;
18
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000019import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback;
Neda Topoljanac19f29162018-10-22 18:12:16 +010020import android.app.admin.StartInstallingUpdateCallback;
21import android.content.Context;
22import android.os.ParcelFileDescriptor;
23import android.os.UpdateEngine;
24import android.os.UpdateEngineCallback;
25import android.util.Log;
26
27import java.io.BufferedReader;
28import java.io.IOException;
29import java.io.InputStreamReader;
30import java.nio.file.Paths;
31import java.util.ArrayList;
32import java.util.Enumeration;
33import java.util.HashMap;
34import java.util.List;
35import java.util.Map;
36import java.util.zip.ZipEntry;
37import java.util.zip.ZipException;
38import 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 */
46class 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
63 private static Map<Integer, Integer> buildErrorCodesMap() {
64 Map<Integer, Integer> map = new HashMap<>();
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000065 map.put(
66 UpdateEngine.ErrorCodeConstants.ERROR,
67 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
Neda Topoljanac19f29162018-10-22 18:12:16 +010068 map.put(
69 DOWNLOAD_STATE_INITIALIZATION_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000070 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
Alex Kershaw7bcb2fa2019-01-04 15:17:39 +000071 map.put(
72 UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000073 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
Neda Topoljanac19f29162018-10-22 18:12:16 +010074
75 // Error constants corresponding to errors related to bad update file.
76 map.put(
77 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000078 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
Neda Topoljanac19f29162018-10-22 18:12:16 +010079 map.put(
80 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000081 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
Neda Topoljanac19f29162018-10-22 18:12:16 +010082 map.put(
83 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000084 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
Neda Topoljanac19f29162018-10-22 18:12:16 +010085 map.put(
86 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000087 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
Neda Topoljanac19f29162018-10-22 18:12:16 +010088
89 // Error constants corresponding to errors related to devices bad state.
90 map.put(
91 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000092 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
Neda Topoljanac19f29162018-10-22 18:12:16 +010093 map.put(
94 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000095 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
Neda Topoljanac19f29162018-10-22 18:12:16 +010096 map.put(
97 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +000098 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
Neda Topoljanac19f29162018-10-22 18:12:16 +010099 map.put(
100 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000101 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
Neda Topoljanac19f29162018-10-22 18:12:16 +0100102
103 return map;
104 }
105
106 private static Map<Integer, String> buildErrorStringsMap() {
107 Map<Integer, String> map = new HashMap<>();
108 map.put(UpdateEngine.ErrorCodeConstants.ERROR, UNKNOWN_ERROR);
109 map.put(
110 DOWNLOAD_STATE_INITIALIZATION_ERROR,
111 "The delta update payload was targeted for another version or the source partition"
112 + "was modified after it was installed");
113 map.put(
114 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
115 "Failed to finish the configured postinstall works.");
116 map.put(
117 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
118 "Failed to open one of the partitions it tried to write to or read data from.");
119 map.put(
120 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
121 "Payload mismatch error.");
122 map.put(
123 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
124 "Failed to read the payload data from the given URL.");
125 map.put(
126 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, "Payload hash error.");
127 map.put(
128 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
129 "Payload size mismatch error.");
130 map.put(
131 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
132 "Failed to verify the signature of the payload.");
133 map.put(
134 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
135 "The payload has been successfully installed,"
136 + "but the active slot was not flipped.");
137 return map;
138 }
139
140 AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
141 StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
142 DevicePolicyConstants constants) {
143 super(context, updateFileDescriptor, callback, injector, constants);
144 mUpdateInstalled = false;
145 }
146
147 @Override
148 public void installUpdateInThread() {
149 if (mUpdateInstalled) {
150 throw new IllegalStateException("installUpdateInThread can be called only once.");
151 }
152 try {
153 setState();
154 applyPayload(Paths.get(mCopiedUpdateFile.getAbsolutePath()).toUri().toString());
155 } catch (ZipException e) {
156 Log.w(UpdateInstaller.TAG, e);
157 notifyCallbackOnError(
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000158 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
Neda Topoljanac19f29162018-10-22 18:12:16 +0100159 Log.getStackTraceString(e));
160 } catch (IOException e) {
161 Log.w(UpdateInstaller.TAG, e);
162 notifyCallbackOnError(
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000163 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN,
164 Log.getStackTraceString(e));
Neda Topoljanac19f29162018-10-22 18:12:16 +0100165 }
166 }
167
168 private void setState() throws IOException {
169 mUpdateInstalled = true;
170 mPackedUpdateFile = new ZipFile(mCopiedUpdateFile);
171 mProperties = new ArrayList<>();
172 mSizeForUpdate = -1;
173 mOffsetForUpdate = 0;
174 mEntries = mPackedUpdateFile.entries();
175 }
176
177 private UpdateEngine buildBoundUpdateEngine() {
178 UpdateEngine updateEngine = new UpdateEngine();
179 updateEngine.bind(new DelegatingUpdateEngineCallback(this, updateEngine));
180 return updateEngine;
181 }
182
183 private void applyPayload(String updatePath) throws IOException {
184 if (!updateStateForPayload()) {
185 return;
186 }
187 String[] headerKeyValuePairs = mProperties.stream().toArray(String[]::new);
188 if (mSizeForUpdate == -1) {
189 Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package.");
190 notifyCallbackOnError(
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000191 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
Neda Topoljanac19f29162018-10-22 18:12:16 +0100192 "Failed to find payload entry in the given package.");
193 return;
194 }
195
196 UpdateEngine updateEngine = buildBoundUpdateEngine();
197 updateEngine.applyPayload(
198 updatePath, mOffsetForUpdate, mSizeForUpdate, headerKeyValuePairs);
199 }
200
201 private boolean updateStateForPayload() throws IOException {
202 long offset = 0;
203 while (mEntries.hasMoreElements()) {
204 ZipEntry entry = mEntries.nextElement();
205
206 String name = entry.getName();
207 offset += buildOffsetForEntry(entry, name);
208 if (entry.isDirectory()) {
209 offset -= entry.getCompressedSize();
210 continue;
211 }
212 if (PAYLOAD_BIN.equals(name)) {
213 if (entry.getMethod() != ZipEntry.STORED) {
214 Log.w(UpdateInstaller.TAG, "Invalid compression method.");
215 notifyCallbackOnError(
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000216 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
Neda Topoljanac19f29162018-10-22 18:12:16 +0100217 "Invalid compression method.");
218 return false;
219 }
220 mSizeForUpdate = entry.getCompressedSize();
221 mOffsetForUpdate = offset - entry.getCompressedSize();
222 } else if (PAYLOAD_PROPERTIES_TXT.equals(name)) {
223 updatePropertiesForEntry(entry);
224 }
225 }
226 return true;
227 }
228
229 private long buildOffsetForEntry(ZipEntry entry, String name) {
230 return OFFSET_TO_FILE_NAME + name.length() + entry.getCompressedSize()
231 + (entry.getExtra() == null ? 0 : entry.getExtra().length);
232 }
233
234 private void updatePropertiesForEntry(ZipEntry entry) throws IOException {
235 try (BufferedReader bufferedReader = new BufferedReader(
236 new InputStreamReader(mPackedUpdateFile.getInputStream(entry)))) {
237 String line;
238 /* Neither @line nor @mProperties are size constraint since there is a few properties
239 with limited size. */
240 while ((line = bufferedReader.readLine()) != null) {
241 mProperties.add(line);
242 }
243 }
244 }
245
246 private static class DelegatingUpdateEngineCallback extends UpdateEngineCallback {
247 private UpdateInstaller mUpdateInstaller;
248 private UpdateEngine mUpdateEngine;
249
250 DelegatingUpdateEngineCallback(
251 UpdateInstaller updateInstaller, UpdateEngine updateEngine) {
252 mUpdateInstaller = updateInstaller;
253 mUpdateEngine = updateEngine;
254 }
255
256 @Override
257 public void onStatusUpdate(int statusCode, float percentage) {
258 return;
259 }
260
261 @Override
262 public void onPayloadApplicationComplete(int errorCode) {
263 mUpdateEngine.unbind();
264 if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) {
265 mUpdateInstaller.notifyCallbackOnSuccess();
266 } else {
267 mUpdateInstaller.notifyCallbackOnError(
268 errorCodesMap.getOrDefault(
Alex Kershaw97d0e8f2019-02-11 14:58:46 +0000269 errorCode, InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN),
Neda Topoljanac19f29162018-10-22 18:12:16 +0100270 errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode));
271 }
272 }
273 }
274}