blob: 3d60dcf6ac8e5d6af5e92aef4d3efcc4033bb855 [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 Fuller87b11282017-06-23 16:43:45 +010040import java.io.FileDescriptor;
Neil Fuller54525bf2017-06-22 14:10:29 +010041import java.io.FileInputStream;
Neil Fuller68f66662017-03-16 18:32:21 +000042import java.io.IOException;
Neil Fuller54525bf2017-06-22 14:10:29 +010043import java.io.InputStream;
Neil Fuller87b11282017-06-23 16:43:45 +010044import java.io.PrintWriter;
Neil Fuller68f66662017-03-16 18:32:21 +000045import java.util.Arrays;
46import java.util.concurrent.Executor;
47import java.util.concurrent.atomic.AtomicBoolean;
Neil Fuller87b11282017-06-23 16:43:45 +010048import libcore.icu.ICU;
49import libcore.util.ZoneInfoDB;
50
51import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
52import static android.app.timezone.RulesState.DISTRO_STATUS_NONE;
53import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN;
54import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL;
55import static android.app.timezone.RulesState.STAGED_OPERATION_NONE;
56import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL;
57import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN;
Neil Fuller68f66662017-03-16 18:32:21 +000058
59// TODO(nfuller) Add EventLog calls where useful in the system server.
60// TODO(nfuller) Check logging best practices in the system server.
61// TODO(nfuller) Check error handling best practices in the system server.
62public final class RulesManagerService extends IRulesManager.Stub {
63
64 private static final String TAG = "timezone.RulesManagerService";
65
66 /** The distro format supported by this device. */
67 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
68 static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
69 new DistroFormatVersion(
70 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
71 DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
72
73 public static class Lifecycle extends SystemService {
74 private RulesManagerService mService;
75
76 public Lifecycle(Context context) {
77 super(context);
78 }
79
80 @Override
81 public void onStart() {
82 mService = RulesManagerService.create(getContext());
83 mService.start();
84
85 publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
86 }
87 }
88
89 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
90 static final String REQUIRED_UPDATER_PERMISSION =
91 android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
92 private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
93 private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
94
95 private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
96 private final PermissionHelper mPermissionHelper;
97 private final PackageTracker mPackageTracker;
98 private final Executor mExecutor;
99 private final TimeZoneDistroInstaller mInstaller;
Neil Fuller68f66662017-03-16 18:32:21 +0000100
101 private static RulesManagerService create(Context context) {
102 RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
103 return new RulesManagerService(
104 helper /* permissionHelper */,
105 helper /* executor */,
Neil Fuller68f66662017-03-16 18:32:21 +0000106 PackageTracker.create(context),
107 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
108 }
109
110 // A constructor that can be used by tests to supply mocked / faked dependencies.
111 RulesManagerService(PermissionHelper permissionHelper,
Neil Fuller54525bf2017-06-22 14:10:29 +0100112 Executor executor, PackageTracker packageTracker,
Neil Fuller68f66662017-03-16 18:32:21 +0000113 TimeZoneDistroInstaller timeZoneDistroInstaller) {
114 mPermissionHelper = permissionHelper;
115 mExecutor = executor;
Neil Fuller68f66662017-03-16 18:32:21 +0000116 mPackageTracker = packageTracker;
117 mInstaller = timeZoneDistroInstaller;
118 }
119
120 public void start() {
121 mPackageTracker.start();
122 }
123
124 @Override // Binder call
125 public RulesState getRulesState() {
126 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
127
Neil Fuller87b11282017-06-23 16:43:45 +0100128 return getRulesStateInternal();
129 }
130
131 /** Like {@link #getRulesState()} without the permission check. */
132 private RulesState getRulesStateInternal() {
Neil Fuller68f66662017-03-16 18:32:21 +0000133 synchronized(this) {
134 String systemRulesVersion;
135 try {
136 systemRulesVersion = mInstaller.getSystemRulesVersion();
137 } catch (IOException e) {
138 Slog.w(TAG, "Failed to read system rules", e);
139 return null;
140 }
141
142 boolean operationInProgress = this.mOperationInProgress.get();
143
144 // Determine the staged operation status, if possible.
145 DistroRulesVersion stagedDistroRulesVersion = null;
Neil Fuller87b11282017-06-23 16:43:45 +0100146 int stagedOperationStatus = STAGED_OPERATION_UNKNOWN;
Neil Fuller68f66662017-03-16 18:32:21 +0000147 if (!operationInProgress) {
148 StagedDistroOperation stagedDistroOperation;
149 try {
150 stagedDistroOperation = mInstaller.getStagedDistroOperation();
151 if (stagedDistroOperation == null) {
Neil Fuller87b11282017-06-23 16:43:45 +0100152 stagedOperationStatus = STAGED_OPERATION_NONE;
Neil Fuller68f66662017-03-16 18:32:21 +0000153 } else if (stagedDistroOperation.isUninstall) {
Neil Fuller87b11282017-06-23 16:43:45 +0100154 stagedOperationStatus = STAGED_OPERATION_UNINSTALL;
Neil Fuller68f66662017-03-16 18:32:21 +0000155 } else {
156 // Must be an install.
Neil Fuller87b11282017-06-23 16:43:45 +0100157 stagedOperationStatus = STAGED_OPERATION_INSTALL;
Neil Fuller68f66662017-03-16 18:32:21 +0000158 DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
159 stagedDistroRulesVersion = new DistroRulesVersion(
160 stagedDistroVersion.rulesVersion,
161 stagedDistroVersion.revision);
162 }
163 } catch (DistroException | IOException e) {
164 Slog.w(TAG, "Failed to read staged distro.", e);
165 }
166 }
167
168 // Determine the installed distro state, if possible.
169 DistroVersion installedDistroVersion;
Neil Fuller87b11282017-06-23 16:43:45 +0100170 int distroStatus = DISTRO_STATUS_UNKNOWN;
Neil Fuller68f66662017-03-16 18:32:21 +0000171 DistroRulesVersion installedDistroRulesVersion = null;
172 if (!operationInProgress) {
173 try {
174 installedDistroVersion = mInstaller.getInstalledDistroVersion();
175 if (installedDistroVersion == null) {
Neil Fuller87b11282017-06-23 16:43:45 +0100176 distroStatus = DISTRO_STATUS_NONE;
Neil Fuller68f66662017-03-16 18:32:21 +0000177 installedDistroRulesVersion = null;
178 } else {
Neil Fuller87b11282017-06-23 16:43:45 +0100179 distroStatus = DISTRO_STATUS_INSTALLED;
Neil Fuller68f66662017-03-16 18:32:21 +0000180 installedDistroRulesVersion = new DistroRulesVersion(
181 installedDistroVersion.rulesVersion,
182 installedDistroVersion.revision);
183 }
184 } catch (DistroException | IOException e) {
185 Slog.w(TAG, "Failed to read installed distro.", e);
186 }
187 }
188 return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
189 operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
190 distroStatus, installedDistroRulesVersion);
191 }
192 }
193
194 @Override
Neil Fuller54525bf2017-06-22 14:10:29 +0100195 public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor,
196 byte[] checkTokenBytes, ICallback callback) {
Neil Fuller68f66662017-03-16 18:32:21 +0000197
Neil Fuller54525bf2017-06-22 14:10:29 +0100198 boolean closeParcelFileDescriptorOnExit = true;
199 try {
200 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
Neil Fuller68f66662017-03-16 18:32:21 +0000201
Neil Fuller54525bf2017-06-22 14:10:29 +0100202 CheckToken checkToken = null;
203 if (checkTokenBytes != null) {
204 checkToken = createCheckTokenOrThrow(checkTokenBytes);
205 }
Neil Fuller68f66662017-03-16 18:32:21 +0000206
Neil Fuller54525bf2017-06-22 14:10:29 +0100207 synchronized (this) {
208 if (distroParcelFileDescriptor == null) {
209 throw new NullPointerException("distroParcelFileDescriptor == null");
210 }
211 if (callback == null) {
212 throw new NullPointerException("observer == null");
213 }
214 if (mOperationInProgress.get()) {
215 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
216 }
217 mOperationInProgress.set(true);
218
219 // Execute the install asynchronously.
220 mExecutor.execute(
221 new InstallRunnable(distroParcelFileDescriptor, checkToken, callback));
222
223 // The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after
224 // it executes (and we do not have to).
225 closeParcelFileDescriptorOnExit = false;
226
227 return RulesManager.SUCCESS;
228 }
229 } finally {
230 // We should close() the local ParcelFileDescriptor we were passed if it hasn't been
231 // passed to another thread to handle.
232 if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) {
233 try {
234 distroParcelFileDescriptor.close();
235 } catch (IOException e) {
236 Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e);
237 }
238 }
Neil Fuller68f66662017-03-16 18:32:21 +0000239 }
240 }
241
242 private class InstallRunnable implements Runnable {
243
Neil Fuller54525bf2017-06-22 14:10:29 +0100244 private final ParcelFileDescriptor mDistroParcelFileDescriptor;
Neil Fuller68f66662017-03-16 18:32:21 +0000245 private final CheckToken mCheckToken;
246 private final ICallback mCallback;
247
Neil Fuller54525bf2017-06-22 14:10:29 +0100248 InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken,
249 ICallback callback) {
250 mDistroParcelFileDescriptor = distroParcelFileDescriptor;
Neil Fuller68f66662017-03-16 18:32:21 +0000251 mCheckToken = checkToken;
252 mCallback = callback;
253 }
254
255 @Override
256 public void run() {
Neil Fuller54525bf2017-06-22 14:10:29 +0100257 boolean success = false;
Neil Fuller68f66662017-03-16 18:32:21 +0000258 // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
259 // when we are done.
Neil Fuller54525bf2017-06-22 14:10:29 +0100260 try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) {
261 // The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close
262 // it at the end of the try-with-resources.
263 final boolean isFdOwner = false;
264 InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner);
265
266 TimeZoneDistro distro = new TimeZoneDistro(is);
Neil Fullerfe3b1182017-06-19 12:56:08 +0100267 int installerResult = mInstaller.stageInstallWithErrorCode(distro);
Neil Fuller68f66662017-03-16 18:32:21 +0000268 int resultCode = mapInstallerResultToApiCode(installerResult);
269 sendFinishedStatus(mCallback, resultCode);
270
271 // All the installer failure modes are currently non-recoverable and won't be
272 // improved by trying again. Therefore success = true.
273 success = true;
274 } catch (Exception e) {
275 Slog.w(TAG, "Failed to install distro.", e);
276 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
277 } finally {
278 // Notify the package tracker that the operation is now complete.
279 mPackageTracker.recordCheckResult(mCheckToken, success);
280
281 mOperationInProgress.set(false);
282 }
283 }
284
285 private int mapInstallerResultToApiCode(int installerResult) {
286 switch (installerResult) {
287 case TimeZoneDistroInstaller.INSTALL_SUCCESS:
288 return Callback.SUCCESS;
289 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
290 return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
291 case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
292 return Callback.ERROR_INSTALL_RULES_TOO_OLD;
293 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
294 return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
295 case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
296 return Callback.ERROR_INSTALL_VALIDATION_ERROR;
297 default:
298 return Callback.ERROR_UNKNOWN_FAILURE;
299 }
300 }
301 }
302
303 @Override
304 public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
305 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
306
307 CheckToken checkToken = null;
308 if (checkTokenBytes != null) {
309 checkToken = createCheckTokenOrThrow(checkTokenBytes);
310 }
311 synchronized(this) {
312 if (callback == null) {
313 throw new NullPointerException("callback == null");
314 }
315
316 if (mOperationInProgress.get()) {
317 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
318 }
319 mOperationInProgress.set(true);
320
321 // Execute the uninstall asynchronously.
322 mExecutor.execute(new UninstallRunnable(checkToken, callback));
323
324 return RulesManager.SUCCESS;
325 }
326 }
327
328 private class UninstallRunnable implements Runnable {
329
330 private final CheckToken mCheckToken;
331 private final ICallback mCallback;
332
333 public UninstallRunnable(CheckToken checkToken, ICallback callback) {
334 mCheckToken = checkToken;
335 mCallback = callback;
336 }
337
338 @Override
339 public void run() {
340 boolean success = false;
341 try {
342 success = mInstaller.stageUninstall();
343 // Right now we just have success (0) / failure (1). All clients should be checking
344 // against SUCCESS. More granular failures may be added in future.
345 int resultCode = success ? Callback.SUCCESS
346 : Callback.ERROR_UNKNOWN_FAILURE;
347 sendFinishedStatus(mCallback, resultCode);
348 } catch (Exception e) {
349 Slog.w(TAG, "Failed to uninstall distro.", e);
350 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
351 } finally {
352 // Notify the package tracker that the operation is now complete.
353 mPackageTracker.recordCheckResult(mCheckToken, success);
354
355 mOperationInProgress.set(false);
356 }
357 }
358 }
359
360 private void sendFinishedStatus(ICallback callback, int resultCode) {
361 try {
362 callback.onFinished(resultCode);
363 } catch (RemoteException e) {
364 Slog.e(TAG, "Unable to notify observer of result", e);
365 }
366 }
367
368 @Override
369 public void requestNothing(byte[] checkTokenBytes, boolean success) {
370 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
371 CheckToken checkToken = null;
372 if (checkTokenBytes != null) {
373 checkToken = createCheckTokenOrThrow(checkTokenBytes);
374 }
375 mPackageTracker.recordCheckResult(checkToken, success);
376 }
377
Neil Fuller87b11282017-06-23 16:43:45 +0100378 @Override
379 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
380 if (!mPermissionHelper.checkDumpPermission(TAG, pw)) {
381 return;
382 }
383
384 RulesState rulesState = getRulesStateInternal();
385 if (args != null && args.length == 2) {
386 // Formatting options used for automated tests. The format is less free-form than
387 // the -format options, which are intended to be easier to parse.
388 if ("-format_state".equals(args[0]) && args[1] != null) {
389 for (char c : args[1].toCharArray()) {
390 switch (c) {
391 case 'p': // Report operation in progress
392 pw.println("Operation in progress: "
393 + rulesState.isOperationInProgress());
394 break;
395 case 's': // Report system image rules version
396 pw.println("System rules version: "
397 + rulesState.getSystemRulesVersion());
398 break;
399 case 'c': // Report current installation state
400 pw.println("Current install state: "
401 + distroStatusToString(rulesState.getDistroStatus()));
402 break;
403 case 'i': // Report currently installed version
404 DistroRulesVersion installedRulesVersion =
405 rulesState.getInstalledDistroRulesVersion();
406 pw.print("Installed rules version: ");
407 if (installedRulesVersion == null) {
408 pw.println("<None>");
409 } else {
410 pw.println(installedRulesVersion.toDumpString());
411 }
412 break;
413 case 'o': // Report staged operation type
414 int stagedOperationType = rulesState.getStagedOperationType();
415 pw.println("Staged operation: "
416 + stagedOperationToString(stagedOperationType));
417 break;
418 case 't':
419 // Report staged version (i.e. the one that will be installed next boot
420 // if the staged operation is an install).
421 pw.print("Staged rules version: ");
422 DistroRulesVersion stagedDistroRulesVersion =
423 rulesState.getStagedDistroRulesVersion();
424 if (stagedDistroRulesVersion == null) {
425 pw.println("<None>");
426 } else {
427 pw.println("Staged install version: "
428 + stagedDistroRulesVersion.toDumpString());
429 }
430 break;
431 case 'a':
432 // Report the active rules version (i.e. the rules in use by the current
433 // process).
434 pw.println("Active rules version (ICU, libcore): "
435 + ICU.getTZDataVersion() + ","
436 + ZoneInfoDB.getInstance().getVersion());
437 break;
438 default:
439 pw.println("Unknown option: " + c);
440 }
441 }
442 return;
443 }
444 }
445
446 pw.println("RulesManagerService state: " + toString());
447 pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
448 + ZoneInfoDB.getInstance().getVersion());
449 mPackageTracker.dump(pw);
450 }
451
452 @Override
453 public String toString() {
454 return "RulesManagerService{" +
455 "mOperationInProgress=" + mOperationInProgress +
456 '}';
457 }
458
Neil Fuller68f66662017-03-16 18:32:21 +0000459 private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
460 CheckToken checkToken;
461 try {
462 checkToken = CheckToken.fromByteArray(checkTokenBytes);
463 } catch (IOException e) {
464 throw new IllegalArgumentException("Unable to read token bytes "
465 + Arrays.toString(checkTokenBytes), e);
466 }
467 return checkToken;
468 }
Neil Fuller87b11282017-06-23 16:43:45 +0100469
470 private static String distroStatusToString(int distroStatus) {
471 switch(distroStatus) {
472 case DISTRO_STATUS_NONE:
473 return "None";
474 case DISTRO_STATUS_INSTALLED:
475 return "Installed";
476 case DISTRO_STATUS_UNKNOWN:
477 default:
478 return "Unknown";
479 }
480 }
481
482 private static String stagedOperationToString(int stagedOperationType) {
483 switch(stagedOperationType) {
484 case STAGED_OPERATION_NONE:
485 return "None";
486 case STAGED_OPERATION_UNINSTALL:
487 return "Uninstall";
488 case STAGED_OPERATION_INSTALL:
489 return "Install";
490 case STAGED_OPERATION_UNKNOWN:
491 default:
492 return "Unknown";
493 }
494 }
Neil Fuller68f66662017-03-16 18:32:21 +0000495}