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