Home | History | Annotate | Download | only in server
      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 
     46 import org.xmlpull.v1.XmlPullParser;
     47 import org.xmlpull.v1.XmlPullParserException;
     48 import org.xmlpull.v1.XmlSerializer;
     49 
     50 import java.io.File;
     51 import java.io.FileInputStream;
     52 import java.io.FileNotFoundException;
     53 import java.io.FileOutputStream;
     54 import java.io.IOException;
     55 import java.nio.charset.StandardCharsets;
     56 
     57 public 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 }
    256