blob: 5724398210e4df88504dd5e427810a614eff96ef [file] [log] [blame]
Neil Fuller68f66662017-03-16 18:32:21 +00001/*
2 * Copyright (C) 2017 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.timezone;
18
19import com.android.internal.annotations.VisibleForTesting;
20import com.android.server.SystemService;
Neil Fullera6a71d02017-06-13 15:12:17 +010021import com.android.timezone.distro.DistroException;
22import com.android.timezone.distro.DistroVersion;
23import com.android.timezone.distro.StagedDistroOperation;
Neil Fuller54525bf2017-06-22 14:10:29 +010024import com.android.timezone.distro.TimeZoneDistro;
Neil Fuller5ca8b5b2017-06-29 12:39:49 +010025import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
Neil Fuller68f66662017-03-16 18:32:21 +000026
27import android.app.timezone.Callback;
28import android.app.timezone.DistroFormatVersion;
29import android.app.timezone.DistroRulesVersion;
30import android.app.timezone.ICallback;
31import android.app.timezone.IRulesManager;
32import android.app.timezone.RulesManager;
33import android.app.timezone.RulesState;
34import android.content.Context;
35import android.os.ParcelFileDescriptor;
36import android.os.RemoteException;
37import android.util.Slog;
38
39import java.io.File;
Neil Fuller54525bf2017-06-22 14:10:29 +010040import java.io.FileInputStream;
Neil Fuller68f66662017-03-16 18:32:21 +000041import java.io.IOException;
Neil Fuller54525bf2017-06-22 14:10:29 +010042import java.io.InputStream;
Neil Fuller68f66662017-03-16 18:32:21 +000043import java.util.Arrays;
44import java.util.concurrent.Executor;
45import java.util.concurrent.atomic.AtomicBoolean;
Neil Fuller68f66662017-03-16 18:32:21 +000046
47// TODO(nfuller) Add EventLog calls where useful in the system server.
48// TODO(nfuller) Check logging best practices in the system server.
49// TODO(nfuller) Check error handling best practices in the system server.
50public final class RulesManagerService extends IRulesManager.Stub {
51
52 private static final String TAG = "timezone.RulesManagerService";
53
54 /** The distro format supported by this device. */
55 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
56 static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
57 new DistroFormatVersion(
58 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
59 DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
60
61 public static class Lifecycle extends SystemService {
62 private RulesManagerService mService;
63
64 public Lifecycle(Context context) {
65 super(context);
66 }
67
68 @Override
69 public void onStart() {
70 mService = RulesManagerService.create(getContext());
71 mService.start();
72
73 publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
74 }
75 }
76
77 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
78 static final String REQUIRED_UPDATER_PERMISSION =
79 android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
80 private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
81 private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
82
83 private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
84 private final PermissionHelper mPermissionHelper;
85 private final PackageTracker mPackageTracker;
86 private final Executor mExecutor;
87 private final TimeZoneDistroInstaller mInstaller;
Neil Fuller68f66662017-03-16 18:32:21 +000088
89 private static RulesManagerService create(Context context) {
90 RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
91 return new RulesManagerService(
92 helper /* permissionHelper */,
93 helper /* executor */,
Neil Fuller68f66662017-03-16 18:32:21 +000094 PackageTracker.create(context),
95 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
96 }
97
98 // A constructor that can be used by tests to supply mocked / faked dependencies.
99 RulesManagerService(PermissionHelper permissionHelper,
Neil Fuller54525bf2017-06-22 14:10:29 +0100100 Executor executor, PackageTracker packageTracker,
Neil Fuller68f66662017-03-16 18:32:21 +0000101 TimeZoneDistroInstaller timeZoneDistroInstaller) {
102 mPermissionHelper = permissionHelper;
103 mExecutor = executor;
Neil Fuller68f66662017-03-16 18:32:21 +0000104 mPackageTracker = packageTracker;
105 mInstaller = timeZoneDistroInstaller;
106 }
107
108 public void start() {
109 mPackageTracker.start();
110 }
111
112 @Override // Binder call
113 public RulesState getRulesState() {
114 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
115
116 synchronized(this) {
117 String systemRulesVersion;
118 try {
119 systemRulesVersion = mInstaller.getSystemRulesVersion();
120 } catch (IOException e) {
121 Slog.w(TAG, "Failed to read system rules", e);
122 return null;
123 }
124
125 boolean operationInProgress = this.mOperationInProgress.get();
126
127 // Determine the staged operation status, if possible.
128 DistroRulesVersion stagedDistroRulesVersion = null;
129 int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
130 if (!operationInProgress) {
131 StagedDistroOperation stagedDistroOperation;
132 try {
133 stagedDistroOperation = mInstaller.getStagedDistroOperation();
134 if (stagedDistroOperation == null) {
135 stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
136 } else if (stagedDistroOperation.isUninstall) {
137 stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
138 } else {
139 // Must be an install.
140 stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
141 DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
142 stagedDistroRulesVersion = new DistroRulesVersion(
143 stagedDistroVersion.rulesVersion,
144 stagedDistroVersion.revision);
145 }
146 } catch (DistroException | IOException e) {
147 Slog.w(TAG, "Failed to read staged distro.", e);
148 }
149 }
150
151 // Determine the installed distro state, if possible.
152 DistroVersion installedDistroVersion;
153 int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
154 DistroRulesVersion installedDistroRulesVersion = null;
155 if (!operationInProgress) {
156 try {
157 installedDistroVersion = mInstaller.getInstalledDistroVersion();
158 if (installedDistroVersion == null) {
159 distroStatus = RulesState.DISTRO_STATUS_NONE;
160 installedDistroRulesVersion = null;
161 } else {
162 distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
163 installedDistroRulesVersion = new DistroRulesVersion(
164 installedDistroVersion.rulesVersion,
165 installedDistroVersion.revision);
166 }
167 } catch (DistroException | IOException e) {
168 Slog.w(TAG, "Failed to read installed distro.", e);
169 }
170 }
171 return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
172 operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
173 distroStatus, installedDistroRulesVersion);
174 }
175 }
176
177 @Override
Neil Fuller54525bf2017-06-22 14:10:29 +0100178 public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor,
179 byte[] checkTokenBytes, ICallback callback) {
Neil Fuller68f66662017-03-16 18:32:21 +0000180
Neil Fuller54525bf2017-06-22 14:10:29 +0100181 boolean closeParcelFileDescriptorOnExit = true;
182 try {
183 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
Neil Fuller68f66662017-03-16 18:32:21 +0000184
Neil Fuller54525bf2017-06-22 14:10:29 +0100185 CheckToken checkToken = null;
186 if (checkTokenBytes != null) {
187 checkToken = createCheckTokenOrThrow(checkTokenBytes);
188 }
Neil Fuller68f66662017-03-16 18:32:21 +0000189
Neil Fuller54525bf2017-06-22 14:10:29 +0100190 synchronized (this) {
191 if (distroParcelFileDescriptor == null) {
192 throw new NullPointerException("distroParcelFileDescriptor == null");
193 }
194 if (callback == null) {
195 throw new NullPointerException("observer == null");
196 }
197 if (mOperationInProgress.get()) {
198 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
199 }
200 mOperationInProgress.set(true);
201
202 // Execute the install asynchronously.
203 mExecutor.execute(
204 new InstallRunnable(distroParcelFileDescriptor, checkToken, callback));
205
206 // The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after
207 // it executes (and we do not have to).
208 closeParcelFileDescriptorOnExit = false;
209
210 return RulesManager.SUCCESS;
211 }
212 } finally {
213 // We should close() the local ParcelFileDescriptor we were passed if it hasn't been
214 // passed to another thread to handle.
215 if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) {
216 try {
217 distroParcelFileDescriptor.close();
218 } catch (IOException e) {
219 Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e);
220 }
221 }
Neil Fuller68f66662017-03-16 18:32:21 +0000222 }
223 }
224
225 private class InstallRunnable implements Runnable {
226
Neil Fuller54525bf2017-06-22 14:10:29 +0100227 private final ParcelFileDescriptor mDistroParcelFileDescriptor;
Neil Fuller68f66662017-03-16 18:32:21 +0000228 private final CheckToken mCheckToken;
229 private final ICallback mCallback;
230
Neil Fuller54525bf2017-06-22 14:10:29 +0100231 InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken,
232 ICallback callback) {
233 mDistroParcelFileDescriptor = distroParcelFileDescriptor;
Neil Fuller68f66662017-03-16 18:32:21 +0000234 mCheckToken = checkToken;
235 mCallback = callback;
236 }
237
238 @Override
239 public void run() {
Neil Fuller54525bf2017-06-22 14:10:29 +0100240 boolean success = false;
Neil Fuller68f66662017-03-16 18:32:21 +0000241 // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
242 // when we are done.
Neil Fuller54525bf2017-06-22 14:10:29 +0100243 try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) {
244 // The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close
245 // it at the end of the try-with-resources.
246 final boolean isFdOwner = false;
247 InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner);
248
249 TimeZoneDistro distro = new TimeZoneDistro(is);
Neil Fullerfe3b1182017-06-19 12:56:08 +0100250 int installerResult = mInstaller.stageInstallWithErrorCode(distro);
Neil Fuller68f66662017-03-16 18:32:21 +0000251 int resultCode = mapInstallerResultToApiCode(installerResult);
252 sendFinishedStatus(mCallback, resultCode);
253
254 // All the installer failure modes are currently non-recoverable and won't be
255 // improved by trying again. Therefore success = true.
256 success = true;
257 } catch (Exception e) {
258 Slog.w(TAG, "Failed to install distro.", e);
259 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
260 } finally {
261 // Notify the package tracker that the operation is now complete.
262 mPackageTracker.recordCheckResult(mCheckToken, success);
263
264 mOperationInProgress.set(false);
265 }
266 }
267
268 private int mapInstallerResultToApiCode(int installerResult) {
269 switch (installerResult) {
270 case TimeZoneDistroInstaller.INSTALL_SUCCESS:
271 return Callback.SUCCESS;
272 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
273 return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
274 case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
275 return Callback.ERROR_INSTALL_RULES_TOO_OLD;
276 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
277 return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
278 case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
279 return Callback.ERROR_INSTALL_VALIDATION_ERROR;
280 default:
281 return Callback.ERROR_UNKNOWN_FAILURE;
282 }
283 }
284 }
285
286 @Override
287 public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
288 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
289
290 CheckToken checkToken = null;
291 if (checkTokenBytes != null) {
292 checkToken = createCheckTokenOrThrow(checkTokenBytes);
293 }
294 synchronized(this) {
295 if (callback == null) {
296 throw new NullPointerException("callback == null");
297 }
298
299 if (mOperationInProgress.get()) {
300 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
301 }
302 mOperationInProgress.set(true);
303
304 // Execute the uninstall asynchronously.
305 mExecutor.execute(new UninstallRunnable(checkToken, callback));
306
307 return RulesManager.SUCCESS;
308 }
309 }
310
311 private class UninstallRunnable implements Runnable {
312
313 private final CheckToken mCheckToken;
314 private final ICallback mCallback;
315
316 public UninstallRunnable(CheckToken checkToken, ICallback callback) {
317 mCheckToken = checkToken;
318 mCallback = callback;
319 }
320
321 @Override
322 public void run() {
323 boolean success = false;
324 try {
325 success = mInstaller.stageUninstall();
326 // Right now we just have success (0) / failure (1). All clients should be checking
327 // against SUCCESS. More granular failures may be added in future.
328 int resultCode = success ? Callback.SUCCESS
329 : Callback.ERROR_UNKNOWN_FAILURE;
330 sendFinishedStatus(mCallback, resultCode);
331 } catch (Exception e) {
332 Slog.w(TAG, "Failed to uninstall distro.", e);
333 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
334 } finally {
335 // Notify the package tracker that the operation is now complete.
336 mPackageTracker.recordCheckResult(mCheckToken, success);
337
338 mOperationInProgress.set(false);
339 }
340 }
341 }
342
343 private void sendFinishedStatus(ICallback callback, int resultCode) {
344 try {
345 callback.onFinished(resultCode);
346 } catch (RemoteException e) {
347 Slog.e(TAG, "Unable to notify observer of result", e);
348 }
349 }
350
351 @Override
352 public void requestNothing(byte[] checkTokenBytes, boolean success) {
353 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
354 CheckToken checkToken = null;
355 if (checkTokenBytes != null) {
356 checkToken = createCheckTokenOrThrow(checkTokenBytes);
357 }
358 mPackageTracker.recordCheckResult(checkToken, success);
359 }
360
361 private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
362 CheckToken checkToken;
363 try {
364 checkToken = CheckToken.fromByteArray(checkTokenBytes);
365 } catch (IOException e) {
366 throw new IllegalArgumentException("Unable to read token bytes "
367 + Arrays.toString(checkTokenBytes), e);
368 }
369 return checkToken;
370 }
371}