1b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra/* 2b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * Copyright (C) 2012 The Android Open Source Project 3b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * 4b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * Licensed under the Apache License, Version 2.0 (the "License"); 5b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * you may not use this file except in compliance with the License. 6b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * You may obtain a copy of the License at 7b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * 8b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * http://www.apache.org/licenses/LICENSE-2.0 9b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * 10b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * Unless required by applicable law or agreed to in writing, software 11b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * distributed under the License is distributed on an "AS IS" BASIS, 12b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * See the License for the specific language governing permissions and 14b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra * limitations under the License. 15b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra */ 16b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 17b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condrapackage com.android.server.updates; 18b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 1912964bf5dd36d13fc25efef7c7175a677f9490a5Ben Gruverimport com.android.server.EventLogTags; 2012964bf5dd36d13fc25efef7c7175a677f9490a5Ben Gruver 21b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport android.content.BroadcastReceiver; 22b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport android.content.Context; 23b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport android.content.Intent; 24103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevichimport android.net.Uri; 257c65e39964a1aa8fffbd940c5ee9e77691aa9656Geremy Condraimport android.util.EventLog; 26b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport android.util.Slog; 27b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 28b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport java.io.File; 29b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport java.io.FileOutputStream; 30b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport java.io.IOException; 3112964bf5dd36d13fc25efef7c7175a677f9490a5Ben Gruverimport java.io.InputStream; 32b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport java.security.MessageDigest; 33b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport java.security.NoSuchAlgorithmException; 34b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 35b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condraimport libcore.io.IoUtils; 36103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevichimport libcore.io.Streams; 37b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 38b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condrapublic class ConfigUpdateInstallReceiver extends BroadcastReceiver { 39b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 40b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private static final String TAG = "ConfigUpdateInstallReceiver"; 41b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 42b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH"; 43b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private static final String EXTRA_VERSION_NUMBER = "VERSION"; 44b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 45a2dffda3b3bc4f0bccb175ef4569e45a221d0eb2Geremy Condra protected final File updateDir; 46a2dffda3b3bc4f0bccb175ef4569e45a221d0eb2Geremy Condra protected final File updateContent; 47a2dffda3b3bc4f0bccb175ef4569e45a221d0eb2Geremy Condra protected final File updateVersion; 48b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 49b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath, 50b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String updateMetadataPath, String updateVersionPath) { 51b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra this.updateDir = new File(updateDir); 52b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra this.updateContent = new File(updateDir, updateContentPath); 53b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra File updateMetadataDir = new File(updateDir, updateMetadataPath); 54b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra this.updateVersion = new File(updateMetadataDir, updateVersionPath); 55b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 56b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 57b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra @Override 58b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra public void onReceive(final Context context, final Intent intent) { 59b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra new Thread() { 60b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra @Override 61b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra public void run() { 62b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra try { 63b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // get the content path from the extras 64103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich byte[] altContent = getAltContent(context, intent); 65b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // get the version from the extras 66b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra int altVersion = getVersionFromIntent(intent); 67b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // get the previous value from the extras 68b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String altRequiredHash = getRequiredHashFromIntent(intent); 69b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // get the version currently being used 70b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra int currentVersion = getCurrentVersion(); 71b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // get the hash of the currently used value 72b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String currentHash = getCurrentHash(getCurrentContent()); 73b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (!verifyVersion(currentVersion, altVersion)) { 740967a9edfc29fe601c9242648b93448d710b7a97Geremy Condra Slog.i(TAG, "Not installing, new version is <= current version"); 75b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } else if (!verifyPreviousHash(currentHash, altRequiredHash)) { 76beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, 77beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra "Current hash did not match required value"); 78b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } else { 79b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // install the new content 80b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra Slog.i(TAG, "Found new update, installing..."); 81b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra install(altContent, altVersion); 82b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra Slog.i(TAG, "Installation successful"); 834e7f7e839e6adb8986114ee5b619030696f910caGeremy Condra postInstall(context, intent); 84b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 85b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } catch (Exception e) { 86b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra Slog.e(TAG, "Could not update content!", e); 87beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra // keep the error message <= 100 chars 88beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra String errMsg = e.toString(); 89beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra if (errMsg.length() > 100) { 90beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra errMsg = errMsg.substring(0, 99); 91beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra } 92beb9d53971af42db178dfdf6bbcd28d3f823c5f8Geremy Condra EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, errMsg); 93b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 94b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 95b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra }.start(); 96b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 97b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 98103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich private Uri getContentFromIntent(Intent i) { 99103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich Uri data = i.getData(); 100103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich if (data == null) { 101b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra throw new IllegalStateException("Missing required content path, ignoring."); 102b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 103103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich return data; 104b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 105b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 106b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private int getVersionFromIntent(Intent i) throws NumberFormatException { 107b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER); 108b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (extraValue == null) { 109b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra throw new IllegalStateException("Missing required version number, ignoring."); 110b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 111b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return Integer.parseInt(extraValue.trim()); 112b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 113b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 114b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private String getRequiredHashFromIntent(Intent i) { 115b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH); 116b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (extraValue == null) { 117b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra throw new IllegalStateException("Missing required previous hash, ignoring."); 118b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 119b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return extraValue.trim(); 120b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 121b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 122b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private int getCurrentVersion() throws NumberFormatException { 123b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra try { 124b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim(); 125b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return Integer.parseInt(strVersion); 126b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } catch (IOException e) { 127c6fa237daeaae6107174a9b9d4f591ea8cd26d86Robert Greenwalt Slog.i(TAG, "Couldn't find current metadata, assuming first update"); 128b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return 0; 129b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 130b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 131b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 132103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich private byte[] getAltContent(Context c, Intent i) throws IOException { 133103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich Uri content = getContentFromIntent(i); 134103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich InputStream is = c.getContentResolver().openInputStream(content); 135103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich try { 136103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich return Streams.readFullyNoClose(is); 137103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich } finally { 138103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich is.close(); 139103173e16da609bcf57a88961ca57a8de17e0ee7Nick Kralevich } 140b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 141b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 142ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra private byte[] getCurrentContent() { 143b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra try { 144ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath()); 145b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } catch (IOException e) { 146c6fa237daeaae6107174a9b9d4f591ea8cd26d86Robert Greenwalt Slog.i(TAG, "Failed to read current content, assuming first update!"); 147b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return null; 148b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 149b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 150b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 151ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra private static String getCurrentHash(byte[] content) { 152b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (content == null) { 153b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return "0"; 154b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 155b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra try { 156b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra MessageDigest dgst = MessageDigest.getInstance("SHA512"); 157ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra byte[] fingerprint = dgst.digest(content); 158b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return IntegralToString.bytesToHexString(fingerprint, false); 159b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } catch (NoSuchAlgorithmException e) { 160b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra throw new AssertionError(e); 161b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 162b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 163b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 164b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private boolean verifyVersion(int current, int alternative) { 165b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return (current < alternative); 166b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 167b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 168b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra private boolean verifyPreviousHash(String current, String required) { 169b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // this is an optional value- if the required field is NONE then we ignore it 170b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (required.equals("NONE")) { 171b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return true; 172b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 173b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // otherwise, verify that we match correctly 174b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra return current.equals(required); 175b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 176b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 177a2dffda3b3bc4f0bccb175ef4569e45a221d0eb2Geremy Condra protected void writeUpdate(File dir, File file, byte[] content) throws IOException { 178b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra FileOutputStream out = null; 179b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra File tmp = null; 180b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra try { 181755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra // create the parents for the destination file 182755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra File parent = file.getParentFile(); 183755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra parent.mkdirs(); 184755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra // check that they were created correctly 185755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra if (!parent.exists()) { 186755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra throw new IOException("Failed to create directory " + parent.getCanonicalPath()); 187755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra } 1889765f94ce7e5473c7e9a081f0e8ee7a07a153ecdGeremy Condra // create the temporary file 1899765f94ce7e5473c7e9a081f0e8ee7a07a153ecdGeremy Condra tmp = File.createTempFile("journal", "", dir); 190b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // mark tmp -rw-r--r-- 191b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra tmp.setReadable(true, false); 192b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // write to it 193b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra out = new FileOutputStream(tmp); 194ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra out.write(content); 195b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // sync to disk 196755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra out.getFD().sync(); 197b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra // atomic rename 198755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra if (!tmp.renameTo(file)) { 199755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra throw new IOException("Failed to atomically rename " + file.getCanonicalPath()); 200755b87742319a9ff689df08cea0137732a8f0b2dGeremy Condra } 201b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } finally { 202b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra if (tmp != null) { 203b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra tmp.delete(); 204b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 205b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra IoUtils.closeQuietly(out); 206b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 207b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 208b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra 20978a4c718e30b6af69fafa6c017af3b2719868631Geremy Condra protected void install(byte[] content, int version) throws IOException { 210b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra writeUpdate(updateDir, updateContent, content); 211ad462d2d1652eb9940aa95d1c4d757734aef508bGeremy Condra writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes()); 212b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra } 2134e7f7e839e6adb8986114ee5b619030696f910caGeremy Condra 2144e7f7e839e6adb8986114ee5b619030696f910caGeremy Condra protected void postInstall(Context context, Intent intent) { 2154e7f7e839e6adb8986114ee5b619030696f910caGeremy Condra } 216b631084613e12e1c6a0ae2ad9446e1284b650ccbGeremy Condra} 217