1 /* 2 * Copyright (C) 2012 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.updates; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Binder; 24 import android.util.EventLog; 25 import android.util.Slog; 26 27 import com.android.internal.util.HexDump; 28 import com.android.server.EventLogTags; 29 30 import libcore.io.IoUtils; 31 import libcore.io.Streams; 32 33 import java.io.BufferedInputStream; 34 import java.io.ByteArrayInputStream; 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.security.MessageDigest; 40 import java.security.NoSuchAlgorithmException; 41 42 public class ConfigUpdateInstallReceiver extends BroadcastReceiver { 43 44 private static final String TAG = "ConfigUpdateInstallReceiver"; 45 46 private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH"; 47 private static final String EXTRA_VERSION_NUMBER = "VERSION"; 48 49 protected final File updateDir; 50 protected final File updateContent; 51 protected final File updateVersion; 52 ConfigUpdateInstallReceiver(String updateDir, String updateContentPath, String updateMetadataPath, String updateVersionPath)53 public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath, 54 String updateMetadataPath, String updateVersionPath) { 55 this.updateDir = new File(updateDir); 56 this.updateContent = new File(updateDir, updateContentPath); 57 File updateMetadataDir = new File(updateDir, updateMetadataPath); 58 this.updateVersion = new File(updateMetadataDir, updateVersionPath); 59 } 60 61 @Override onReceive(final Context context, final Intent intent)62 public void onReceive(final Context context, final Intent intent) { 63 new Thread() { 64 @Override 65 public void run() { 66 try { 67 // get the version from the extras 68 int altVersion = getVersionFromIntent(intent); 69 // get the previous value from the extras 70 String altRequiredHash = getRequiredHashFromIntent(intent); 71 // get the version currently being used 72 int currentVersion = getCurrentVersion(); 73 // get the hash of the currently used value 74 String currentHash = getCurrentHash(getCurrentContent()); 75 if (!verifyVersion(currentVersion, altVersion)) { 76 Slog.i(TAG, "Not installing, new version is <= current version"); 77 } else if (!verifyPreviousHash(currentHash, altRequiredHash)) { 78 EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, 79 "Current hash did not match required value"); 80 } else { 81 // install the new content 82 Slog.i(TAG, "Found new update, installing..."); 83 try (BufferedInputStream altContent = getAltContent(context, intent)) { 84 install(altContent, altVersion); 85 } 86 Slog.i(TAG, "Installation successful"); 87 postInstall(context, intent); 88 } 89 } catch (Exception e) { 90 Slog.e(TAG, "Could not update content!", e); 91 // keep the error message <= 100 chars 92 String errMsg = e.toString(); 93 if (errMsg.length() > 100) { 94 errMsg = errMsg.substring(0, 99); 95 } 96 EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, errMsg); 97 } 98 } 99 }.start(); 100 } 101 getContentFromIntent(Intent i)102 private Uri getContentFromIntent(Intent i) { 103 Uri data = i.getData(); 104 if (data == null) { 105 throw new IllegalStateException("Missing required content path, ignoring."); 106 } 107 return data; 108 } 109 getVersionFromIntent(Intent i)110 private int getVersionFromIntent(Intent i) throws NumberFormatException { 111 String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER); 112 if (extraValue == null) { 113 throw new IllegalStateException("Missing required version number, ignoring."); 114 } 115 return Integer.parseInt(extraValue.trim()); 116 } 117 getRequiredHashFromIntent(Intent i)118 private String getRequiredHashFromIntent(Intent i) { 119 String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH); 120 if (extraValue == null) { 121 throw new IllegalStateException("Missing required previous hash, ignoring."); 122 } 123 return extraValue.trim(); 124 } 125 getCurrentVersion()126 private int getCurrentVersion() throws NumberFormatException { 127 try { 128 String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim(); 129 return Integer.parseInt(strVersion); 130 } catch (IOException e) { 131 Slog.i(TAG, "Couldn't find current metadata, assuming first update"); 132 return 0; 133 } 134 } 135 getAltContent(Context c, Intent i)136 private BufferedInputStream getAltContent(Context c, Intent i) throws IOException { 137 Uri content = getContentFromIntent(i); 138 Binder.allowBlockingForCurrentThread(); 139 try { 140 return new BufferedInputStream(c.getContentResolver().openInputStream(content)); 141 } finally { 142 Binder.defaultBlockingForCurrentThread(); 143 } 144 } 145 getCurrentContent()146 private byte[] getCurrentContent() { 147 try { 148 return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath()); 149 } catch (IOException e) { 150 Slog.i(TAG, "Failed to read current content, assuming first update!"); 151 return null; 152 } 153 } 154 getCurrentHash(byte[] content)155 private static String getCurrentHash(byte[] content) { 156 if (content == null) { 157 return "0"; 158 } 159 try { 160 MessageDigest dgst = MessageDigest.getInstance("SHA512"); 161 byte[] fingerprint = dgst.digest(content); 162 return HexDump.toHexString(fingerprint, false); 163 } catch (NoSuchAlgorithmException e) { 164 throw new AssertionError(e); 165 } 166 } 167 verifyVersion(int current, int alternative)168 protected boolean verifyVersion(int current, int alternative) { 169 return (current < alternative); 170 } 171 verifyPreviousHash(String current, String required)172 private boolean verifyPreviousHash(String current, String required) { 173 // this is an optional value- if the required field is NONE then we ignore it 174 if (required.equals("NONE")) { 175 return true; 176 } 177 // otherwise, verify that we match correctly 178 return current.equals(required); 179 } 180 writeUpdate(File dir, File file, InputStream inputStream)181 protected void writeUpdate(File dir, File file, InputStream inputStream) throws IOException { 182 FileOutputStream out = null; 183 File tmp = null; 184 try { 185 // create the parents for the destination file 186 File parent = file.getParentFile(); 187 parent.mkdirs(); 188 // check that they were created correctly 189 if (!parent.exists()) { 190 throw new IOException("Failed to create directory " + parent.getCanonicalPath()); 191 } 192 193 // Give executable permissions to parent folders. 194 while (!(parent.equals(updateDir))) { 195 parent.setExecutable(true, false); 196 parent = parent.getParentFile(); 197 } 198 199 // create the temporary file 200 tmp = File.createTempFile("journal", "", dir); 201 // mark tmp -rw-r--r-- 202 tmp.setReadable(true, false); 203 // write to it 204 out = new FileOutputStream(tmp); 205 Streams.copy(inputStream, out); 206 // sync to disk 207 out.getFD().sync(); 208 // atomic rename 209 if (!tmp.renameTo(file)) { 210 throw new IOException("Failed to atomically rename " + file.getCanonicalPath()); 211 } 212 } finally { 213 if (tmp != null) { 214 tmp.delete(); 215 } 216 IoUtils.closeQuietly(out); 217 } 218 } 219 install(InputStream inputStream, int version)220 protected void install(InputStream inputStream, int version) throws IOException { 221 writeUpdate(updateDir, updateContent, inputStream); 222 writeUpdate(updateDir, updateVersion, 223 new ByteArrayInputStream(Long.toString(version).getBytes())); 224 } 225 postInstall(Context context, Intent intent)226 protected void postInstall(Context context, Intent intent) { 227 } 228 } 229