blob: cc25c8c5f836ca75177dec9ee9a5ddb64644e868 [file] [log] [blame]
Geremy Condrab6310842012-08-23 22:00:15 -07001/*
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
17package com.android.server.updates;
18
Ben Gruver12964bf2015-04-06 10:55:08 -070019import com.android.server.EventLogTags;
Przemyslaw Szczepaniak3f726042015-10-15 11:04:19 +010020import com.android.internal.util.HexDump;
Ben Gruver12964bf2015-04-06 10:55:08 -070021
Geremy Condrab6310842012-08-23 22:00:15 -070022import android.content.BroadcastReceiver;
Geremy Condrab6310842012-08-23 22:00:15 -070023import android.content.Context;
24import android.content.Intent;
Nick Kralevich103173e2014-06-28 12:18:03 -070025import android.net.Uri;
Geremy Condra7c65e392012-09-11 16:57:17 -070026import android.util.EventLog;
Geremy Condrab6310842012-08-23 22:00:15 -070027import android.util.Slog;
28
Geremy Condrab6310842012-08-23 22:00:15 -070029import java.io.File;
Geremy Condrab6310842012-08-23 22:00:15 -070030import java.io.FileOutputStream;
Geremy Condrab6310842012-08-23 22:00:15 -070031import java.io.IOException;
Ben Gruver12964bf2015-04-06 10:55:08 -070032import java.io.InputStream;
Geremy Condrab6310842012-08-23 22:00:15 -070033import java.security.MessageDigest;
34import java.security.NoSuchAlgorithmException;
Geremy Condrab6310842012-08-23 22:00:15 -070035
36import libcore.io.IoUtils;
Nick Kralevich103173e2014-06-28 12:18:03 -070037import libcore.io.Streams;
Geremy Condrab6310842012-08-23 22:00:15 -070038
39public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
40
41 private static final String TAG = "ConfigUpdateInstallReceiver";
42
Geremy Condrab6310842012-08-23 22:00:15 -070043 private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
Geremy Condrab6310842012-08-23 22:00:15 -070044 private static final String EXTRA_VERSION_NUMBER = "VERSION";
45
Geremy Condraa2dffda2013-04-06 14:01:40 -070046 protected final File updateDir;
47 protected final File updateContent;
48 protected final File updateVersion;
Geremy Condrab6310842012-08-23 22:00:15 -070049
50 public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
51 String updateMetadataPath, String updateVersionPath) {
52 this.updateDir = new File(updateDir);
53 this.updateContent = new File(updateDir, updateContentPath);
54 File updateMetadataDir = new File(updateDir, updateMetadataPath);
55 this.updateVersion = new File(updateMetadataDir, updateVersionPath);
56 }
57
58 @Override
59 public void onReceive(final Context context, final Intent intent) {
60 new Thread() {
61 @Override
62 public void run() {
63 try {
Geremy Condrab6310842012-08-23 22:00:15 -070064 // get the content path from the extras
Nick Kralevich103173e2014-06-28 12:18:03 -070065 byte[] altContent = getAltContent(context, intent);
Geremy Condrab6310842012-08-23 22:00:15 -070066 // get the version from the extras
67 int altVersion = getVersionFromIntent(intent);
68 // get the previous value from the extras
69 String altRequiredHash = getRequiredHashFromIntent(intent);
Geremy Condrab6310842012-08-23 22:00:15 -070070 // get the version currently being used
71 int currentVersion = getCurrentVersion();
72 // get the hash of the currently used value
73 String currentHash = getCurrentHash(getCurrentContent());
74 if (!verifyVersion(currentVersion, altVersion)) {
Geremy Condra0967a9e2012-09-19 21:22:42 -070075 Slog.i(TAG, "Not installing, new version is <= current version");
Geremy Condrab6310842012-08-23 22:00:15 -070076 } else if (!verifyPreviousHash(currentHash, altRequiredHash)) {
Geremy Condrabeb9d5392012-09-17 13:29:56 -070077 EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
78 "Current hash did not match required value");
Geremy Condrab6310842012-08-23 22:00:15 -070079 } else {
80 // install the new content
81 Slog.i(TAG, "Found new update, installing...");
82 install(altContent, altVersion);
83 Slog.i(TAG, "Installation successful");
Geremy Condra4e7f7e82013-03-26 21:09:01 -070084 postInstall(context, intent);
Geremy Condrab6310842012-08-23 22:00:15 -070085 }
86 } catch (Exception e) {
87 Slog.e(TAG, "Could not update content!", e);
Geremy Condrabeb9d5392012-09-17 13:29:56 -070088 // keep the error message <= 100 chars
89 String errMsg = e.toString();
90 if (errMsg.length() > 100) {
91 errMsg = errMsg.substring(0, 99);
92 }
93 EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, errMsg);
Geremy Condrab6310842012-08-23 22:00:15 -070094 }
95 }
96 }.start();
97 }
98
Nick Kralevich103173e2014-06-28 12:18:03 -070099 private Uri getContentFromIntent(Intent i) {
100 Uri data = i.getData();
101 if (data == null) {
Geremy Condrab6310842012-08-23 22:00:15 -0700102 throw new IllegalStateException("Missing required content path, ignoring.");
103 }
Nick Kralevich103173e2014-06-28 12:18:03 -0700104 return data;
Geremy Condrab6310842012-08-23 22:00:15 -0700105 }
106
107 private int getVersionFromIntent(Intent i) throws NumberFormatException {
108 String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER);
109 if (extraValue == null) {
110 throw new IllegalStateException("Missing required version number, ignoring.");
111 }
112 return Integer.parseInt(extraValue.trim());
113 }
114
115 private String getRequiredHashFromIntent(Intent i) {
116 String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH);
117 if (extraValue == null) {
118 throw new IllegalStateException("Missing required previous hash, ignoring.");
119 }
120 return extraValue.trim();
121 }
122
Geremy Condrab6310842012-08-23 22:00:15 -0700123 private int getCurrentVersion() throws NumberFormatException {
124 try {
125 String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim();
126 return Integer.parseInt(strVersion);
127 } catch (IOException e) {
Robert Greenwaltc6fa2372012-09-24 13:57:16 -0700128 Slog.i(TAG, "Couldn't find current metadata, assuming first update");
Geremy Condrab6310842012-08-23 22:00:15 -0700129 return 0;
130 }
131 }
132
Nick Kralevich103173e2014-06-28 12:18:03 -0700133 private byte[] getAltContent(Context c, Intent i) throws IOException {
134 Uri content = getContentFromIntent(i);
135 InputStream is = c.getContentResolver().openInputStream(content);
136 try {
137 return Streams.readFullyNoClose(is);
138 } finally {
139 is.close();
140 }
Geremy Condrab6310842012-08-23 22:00:15 -0700141 }
142
Geremy Condraad462d22013-01-30 11:19:54 -0800143 private byte[] getCurrentContent() {
Geremy Condrab6310842012-08-23 22:00:15 -0700144 try {
Geremy Condraad462d22013-01-30 11:19:54 -0800145 return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
Geremy Condrab6310842012-08-23 22:00:15 -0700146 } catch (IOException e) {
Robert Greenwaltc6fa2372012-09-24 13:57:16 -0700147 Slog.i(TAG, "Failed to read current content, assuming first update!");
Geremy Condrab6310842012-08-23 22:00:15 -0700148 return null;
149 }
150 }
151
Geremy Condraad462d22013-01-30 11:19:54 -0800152 private static String getCurrentHash(byte[] content) {
Geremy Condrab6310842012-08-23 22:00:15 -0700153 if (content == null) {
154 return "0";
155 }
156 try {
157 MessageDigest dgst = MessageDigest.getInstance("SHA512");
Geremy Condraad462d22013-01-30 11:19:54 -0800158 byte[] fingerprint = dgst.digest(content);
Przemyslaw Szczepaniak3f726042015-10-15 11:04:19 +0100159 return HexDump.toHexString(fingerprint, false);
Geremy Condrab6310842012-08-23 22:00:15 -0700160 } catch (NoSuchAlgorithmException e) {
161 throw new AssertionError(e);
162 }
163 }
164
165 private boolean verifyVersion(int current, int alternative) {
166 return (current < alternative);
167 }
168
169 private boolean verifyPreviousHash(String current, String required) {
170 // this is an optional value- if the required field is NONE then we ignore it
171 if (required.equals("NONE")) {
172 return true;
173 }
174 // otherwise, verify that we match correctly
175 return current.equals(required);
176 }
177
Geremy Condraa2dffda2013-04-06 14:01:40 -0700178 protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
Geremy Condrab6310842012-08-23 22:00:15 -0700179 FileOutputStream out = null;
180 File tmp = null;
181 try {
Geremy Condra755b8772012-09-11 01:11:02 -0700182 // create the parents for the destination file
183 File parent = file.getParentFile();
184 parent.mkdirs();
185 // check that they were created correctly
186 if (!parent.exists()) {
187 throw new IOException("Failed to create directory " + parent.getCanonicalPath());
188 }
Geremy Condra9765f942013-04-04 17:48:09 -0700189 // create the temporary file
190 tmp = File.createTempFile("journal", "", dir);
Geremy Condrab6310842012-08-23 22:00:15 -0700191 // mark tmp -rw-r--r--
192 tmp.setReadable(true, false);
193 // write to it
194 out = new FileOutputStream(tmp);
Geremy Condraad462d22013-01-30 11:19:54 -0800195 out.write(content);
Geremy Condrab6310842012-08-23 22:00:15 -0700196 // sync to disk
Geremy Condra755b8772012-09-11 01:11:02 -0700197 out.getFD().sync();
Geremy Condrab6310842012-08-23 22:00:15 -0700198 // atomic rename
Geremy Condra755b8772012-09-11 01:11:02 -0700199 if (!tmp.renameTo(file)) {
200 throw new IOException("Failed to atomically rename " + file.getCanonicalPath());
201 }
Geremy Condrab6310842012-08-23 22:00:15 -0700202 } finally {
203 if (tmp != null) {
204 tmp.delete();
205 }
206 IoUtils.closeQuietly(out);
207 }
208 }
209
Geremy Condra78a4c712013-01-30 11:20:26 -0800210 protected void install(byte[] content, int version) throws IOException {
Geremy Condrab6310842012-08-23 22:00:15 -0700211 writeUpdate(updateDir, updateContent, content);
Geremy Condraad462d22013-01-30 11:19:54 -0800212 writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes());
Geremy Condrab6310842012-08-23 22:00:15 -0700213 }
Geremy Condra4e7f7e82013-03-26 21:09:01 -0700214
215 protected void postInstall(Context context, Intent intent) {
216 }
Geremy Condrab6310842012-08-23 22:00:15 -0700217}