blob: 6c1ffdd32d189dcc42823bd2333b57db3173bd1b [file] [log] [blame]
Tao Bao07342dc2017-01-24 15:08:21 -08001/*
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;
18
19import static android.os.SystemUpdateManager.KEY_STATUS;
20import static android.os.SystemUpdateManager.STATUS_IDLE;
21import static android.os.SystemUpdateManager.STATUS_UNKNOWN;
22
23import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
24import static org.xmlpull.v1.XmlPullParser.END_TAG;
25import static org.xmlpull.v1.XmlPullParser.START_TAG;
26
27import android.Manifest;
28import android.annotation.Nullable;
29import android.content.Context;
30import android.content.pm.PackageManager;
31import android.os.Binder;
32import android.os.Build;
33import android.os.Bundle;
34import android.os.Environment;
35import android.os.ISystemUpdateManager;
36import android.os.PersistableBundle;
37import android.os.SystemUpdateManager;
38import android.provider.Settings;
39import android.util.AtomicFile;
40import android.util.Slog;
41import android.util.Xml;
42
43import com.android.internal.util.FastXmlSerializer;
44import com.android.internal.util.XmlUtils;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import java.io.File;
51import java.io.FileInputStream;
52import java.io.FileNotFoundException;
53import java.io.FileOutputStream;
54import java.io.IOException;
55import java.nio.charset.StandardCharsets;
56
57public class SystemUpdateManagerService extends ISystemUpdateManager.Stub {
58
59 private static final String TAG = "SystemUpdateManagerService";
60
61 private static final int UID_UNKNOWN = -1;
62
63 private static final String INFO_FILE = "system-update-info.xml";
64 private static final int INFO_FILE_VERSION = 0;
65 private static final String TAG_INFO = "info";
66 private static final String KEY_VERSION = "version";
67 private static final String KEY_UID = "uid";
68 private static final String KEY_BOOT_COUNT = "boot-count";
69 private static final String KEY_INFO_BUNDLE = "info-bundle";
70
71 private final Context mContext;
72 private final AtomicFile mFile;
73 private final Object mLock = new Object();
74 private int mLastUid = UID_UNKNOWN;
75 private int mLastStatus = STATUS_UNKNOWN;
76
77 public SystemUpdateManagerService(Context context) {
78 mContext = context;
79 mFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), INFO_FILE));
80
81 // Populate mLastUid and mLastStatus.
82 synchronized (mLock) {
83 loadSystemUpdateInfoLocked();
84 }
85 }
86
87 @Override
88 public void updateSystemUpdateInfo(PersistableBundle infoBundle) {
89 mContext.enforceCallingOrSelfPermission(Manifest.permission.RECOVERY, TAG);
90
91 int status = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN);
92 if (status == STATUS_UNKNOWN) {
93 Slog.w(TAG, "Invalid status info. Ignored");
94 return;
95 }
96
97 // There could be multiple updater apps running on a device. But only one at most should
98 // be active (i.e. with a pending update), with the rest reporting idle status. We will
99 // only accept the reported status if any of the following conditions holds:
100 // a) none has been reported before;
101 // b) the current on-file status was last reported by the same caller;
102 // c) an active update is being reported.
103 int uid = Binder.getCallingUid();
104 if (mLastUid == UID_UNKNOWN || mLastUid == uid || status != STATUS_IDLE) {
105 synchronized (mLock) {
106 saveSystemUpdateInfoLocked(infoBundle, uid);
107 }
108 } else {
109 Slog.i(TAG, "Inactive updater reporting IDLE status. Ignored");
110 }
111 }
112
113 @Override
114 public Bundle retrieveSystemUpdateInfo() {
115 if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_SYSTEM_UPDATE_INFO)
116 == PackageManager.PERMISSION_DENIED
117 && mContext.checkCallingOrSelfPermission(Manifest.permission.RECOVERY)
118 == PackageManager.PERMISSION_DENIED) {
119 throw new SecurityException("Can't read system update info. Requiring "
120 + "READ_SYSTEM_UPDATE_INFO or RECOVERY permission.");
121 }
122
123 synchronized (mLock) {
124 return loadSystemUpdateInfoLocked();
125 }
126 }
127
128 // Reads and validates the info file. Returns the loaded info bundle on success; or a default
129 // info bundle with UNKNOWN status.
130 private Bundle loadSystemUpdateInfoLocked() {
131 PersistableBundle loadedBundle = null;
132 try (FileInputStream fis = mFile.openRead()) {
133 XmlPullParser parser = Xml.newPullParser();
134 parser.setInput(fis, StandardCharsets.UTF_8.name());
135 loadedBundle = readInfoFileLocked(parser);
136 } catch (FileNotFoundException e) {
137 Slog.i(TAG, "No existing info file " + mFile.getBaseFile());
138 } catch (XmlPullParserException e) {
139 Slog.e(TAG, "Failed to parse the info file:", e);
140 } catch (IOException e) {
141 Slog.e(TAG, "Failed to read the info file:", e);
142 }
143
144 // Validate the loaded bundle.
145 if (loadedBundle == null) {
146 return removeInfoFileAndGetDefaultInfoBundleLocked();
147 }
148
149 int version = loadedBundle.getInt(KEY_VERSION, -1);
150 if (version == -1) {
151 Slog.w(TAG, "Invalid info file (invalid version). Ignored");
152 return removeInfoFileAndGetDefaultInfoBundleLocked();
153 }
154
155 int lastUid = loadedBundle.getInt(KEY_UID, -1);
156 if (lastUid == -1) {
157 Slog.w(TAG, "Invalid info file (invalid UID). Ignored");
158 return removeInfoFileAndGetDefaultInfoBundleLocked();
159 }
160
161 int lastBootCount = loadedBundle.getInt(KEY_BOOT_COUNT, -1);
162 if (lastBootCount == -1 || lastBootCount != getBootCount()) {
163 Slog.w(TAG, "Outdated info file. Ignored");
164 return removeInfoFileAndGetDefaultInfoBundleLocked();
165 }
166
167 PersistableBundle infoBundle = loadedBundle.getPersistableBundle(KEY_INFO_BUNDLE);
168 if (infoBundle == null) {
169 Slog.w(TAG, "Invalid info file (missing info). Ignored");
170 return removeInfoFileAndGetDefaultInfoBundleLocked();
171 }
172
173 int lastStatus = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN);
174 if (lastStatus == STATUS_UNKNOWN) {
175 Slog.w(TAG, "Invalid info file (invalid status). Ignored");
176 return removeInfoFileAndGetDefaultInfoBundleLocked();
177 }
178
179 // Everything looks good upon reaching this point.
180 mLastStatus = lastStatus;
181 mLastUid = lastUid;
182 return new Bundle(infoBundle);
183 }
184
185 private void saveSystemUpdateInfoLocked(PersistableBundle infoBundle, int uid) {
186 // Wrap the incoming bundle with extra info (e.g. version, uid, boot count). We use nested
187 // PersistableBundle to avoid manually parsing XML attributes when loading the info back.
188 PersistableBundle outBundle = new PersistableBundle();
189 outBundle.putPersistableBundle(KEY_INFO_BUNDLE, infoBundle);
190 outBundle.putInt(KEY_VERSION, INFO_FILE_VERSION);
191 outBundle.putInt(KEY_UID, uid);
192 outBundle.putInt(KEY_BOOT_COUNT, getBootCount());
193
194 // Only update the info on success.
195 if (writeInfoFileLocked(outBundle)) {
196 mLastUid = uid;
197 mLastStatus = infoBundle.getInt(KEY_STATUS);
198 }
199 }
200
201 // Performs I/O work only, without validating the loaded info.
202 @Nullable
203 private PersistableBundle readInfoFileLocked(XmlPullParser parser)
204 throws XmlPullParserException, IOException {
205 int type;
206 while ((type = parser.next()) != END_DOCUMENT) {
207 if (type == START_TAG && TAG_INFO.equals(parser.getName())) {
208 return PersistableBundle.restoreFromXml(parser);
209 }
210 }
211 return null;
212 }
213
214 private boolean writeInfoFileLocked(PersistableBundle outBundle) {
215 FileOutputStream fos = null;
216 try {
217 fos = mFile.startWrite();
218
219 XmlSerializer out = new FastXmlSerializer();
220 out.setOutput(fos, StandardCharsets.UTF_8.name());
221 out.startDocument(null, true);
222
223 out.startTag(null, TAG_INFO);
224 outBundle.saveToXml(out);
225 out.endTag(null, TAG_INFO);
226
227 out.endDocument();
228 mFile.finishWrite(fos);
229 return true;
230 } catch (IOException | XmlPullParserException e) {
231 Slog.e(TAG, "Failed to save the info file:", e);
232 if (fos != null) {
233 mFile.failWrite(fos);
234 }
235 }
236 return false;
237 }
238
239 private Bundle removeInfoFileAndGetDefaultInfoBundleLocked() {
240 if (mFile.exists()) {
241 Slog.i(TAG, "Removing info file");
242 mFile.delete();
243 }
244
245 mLastStatus = STATUS_UNKNOWN;
246 mLastUid = UID_UNKNOWN;
247 Bundle infoBundle = new Bundle();
248 infoBundle.putInt(KEY_STATUS, STATUS_UNKNOWN);
249 return infoBundle;
250 }
251
252 private int getBootCount() {
253 return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
254 }
255}