1 /* 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 17 package com.android.server.timezone; 18 19 import com.android.internal.annotations.VisibleForTesting; 20 import com.android.server.EventLogTags; 21 import com.android.server.SystemService; 22 import com.android.timezone.distro.DistroException; 23 import com.android.timezone.distro.DistroVersion; 24 import com.android.timezone.distro.StagedDistroOperation; 25 import com.android.timezone.distro.TimeZoneDistro; 26 import com.android.timezone.distro.installer.TimeZoneDistroInstaller; 27 28 import android.app.timezone.Callback; 29 import android.app.timezone.DistroFormatVersion; 30 import android.app.timezone.DistroRulesVersion; 31 import android.app.timezone.ICallback; 32 import android.app.timezone.IRulesManager; 33 import android.app.timezone.RulesManager; 34 import android.app.timezone.RulesState; 35 import android.content.Context; 36 import android.os.ParcelFileDescriptor; 37 import android.os.RemoteException; 38 import android.util.Slog; 39 40 import java.io.File; 41 import java.io.FileDescriptor; 42 import java.io.FileInputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.PrintWriter; 46 import java.util.Arrays; 47 import java.util.concurrent.Executor; 48 import java.util.concurrent.atomic.AtomicBoolean; 49 import libcore.icu.ICU; 50 import libcore.util.ZoneInfoDB; 51 52 import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED; 53 import static android.app.timezone.RulesState.DISTRO_STATUS_NONE; 54 import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN; 55 import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL; 56 import static android.app.timezone.RulesState.STAGED_OPERATION_NONE; 57 import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL; 58 import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN; 59 60 public final class RulesManagerService extends IRulesManager.Stub { 61 62 private static final String TAG = "timezone.RulesManagerService"; 63 64 /** The distro format supported by this device. */ 65 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 66 static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED = 67 new DistroFormatVersion( 68 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION, 69 DistroVersion.CURRENT_FORMAT_MINOR_VERSION); 70 71 public static class Lifecycle extends SystemService { 72 public Lifecycle(Context context) { 73 super(context); 74 } 75 76 @Override 77 public void onStart() { 78 RulesManagerService service = RulesManagerService.create(getContext()); 79 service.start(); 80 81 // Publish the binder service so it can be accessed from other (appropriately 82 // permissioned) processes. 83 publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service); 84 85 // Publish the service instance locally so we can use it directly from within the system 86 // server from TimeZoneUpdateIdler. 87 publishLocalService(RulesManagerService.class, service); 88 } 89 } 90 91 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 92 static final String REQUIRED_UPDATER_PERMISSION = 93 android.Manifest.permission.UPDATE_TIME_ZONE_RULES; 94 private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata"); 95 private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); 96 97 private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false); 98 private final PermissionHelper mPermissionHelper; 99 private final PackageTracker mPackageTracker; 100 private final Executor mExecutor; 101 private final TimeZoneDistroInstaller mInstaller; 102 103 private static RulesManagerService create(Context context) { 104 RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context); 105 return new RulesManagerService( 106 helper /* permissionHelper */, 107 helper /* executor */, 108 PackageTracker.create(context), 109 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR)); 110 } 111 112 // A constructor that can be used by tests to supply mocked / faked dependencies. 113 RulesManagerService(PermissionHelper permissionHelper, 114 Executor executor, PackageTracker packageTracker, 115 TimeZoneDistroInstaller timeZoneDistroInstaller) { 116 mPermissionHelper = permissionHelper; 117 mExecutor = executor; 118 mPackageTracker = packageTracker; 119 mInstaller = timeZoneDistroInstaller; 120 } 121 122 public void start() { 123 mPackageTracker.start(); 124 } 125 126 @Override // Binder call 127 public RulesState getRulesState() { 128 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 129 130 return getRulesStateInternal(); 131 } 132 133 /** Like {@link #getRulesState()} without the permission check. */ 134 private RulesState getRulesStateInternal() { 135 synchronized(this) { 136 String systemRulesVersion; 137 try { 138 systemRulesVersion = mInstaller.getSystemRulesVersion(); 139 } catch (IOException e) { 140 Slog.w(TAG, "Failed to read system rules", e); 141 return null; 142 } 143 144 boolean operationInProgress = this.mOperationInProgress.get(); 145 146 // Determine the staged operation status, if possible. 147 DistroRulesVersion stagedDistroRulesVersion = null; 148 int stagedOperationStatus = STAGED_OPERATION_UNKNOWN; 149 if (!operationInProgress) { 150 StagedDistroOperation stagedDistroOperation; 151 try { 152 stagedDistroOperation = mInstaller.getStagedDistroOperation(); 153 if (stagedDistroOperation == null) { 154 stagedOperationStatus = STAGED_OPERATION_NONE; 155 } else if (stagedDistroOperation.isUninstall) { 156 stagedOperationStatus = STAGED_OPERATION_UNINSTALL; 157 } else { 158 // Must be an install. 159 stagedOperationStatus = STAGED_OPERATION_INSTALL; 160 DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion; 161 stagedDistroRulesVersion = new DistroRulesVersion( 162 stagedDistroVersion.rulesVersion, 163 stagedDistroVersion.revision); 164 } 165 } catch (DistroException | IOException e) { 166 Slog.w(TAG, "Failed to read staged distro.", e); 167 } 168 } 169 170 // Determine the installed distro state, if possible. 171 DistroVersion installedDistroVersion; 172 int distroStatus = DISTRO_STATUS_UNKNOWN; 173 DistroRulesVersion installedDistroRulesVersion = null; 174 if (!operationInProgress) { 175 try { 176 installedDistroVersion = mInstaller.getInstalledDistroVersion(); 177 if (installedDistroVersion == null) { 178 distroStatus = DISTRO_STATUS_NONE; 179 installedDistroRulesVersion = null; 180 } else { 181 distroStatus = DISTRO_STATUS_INSTALLED; 182 installedDistroRulesVersion = new DistroRulesVersion( 183 installedDistroVersion.rulesVersion, 184 installedDistroVersion.revision); 185 } 186 } catch (DistroException | IOException e) { 187 Slog.w(TAG, "Failed to read installed distro.", e); 188 } 189 } 190 return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED, 191 operationInProgress, stagedOperationStatus, stagedDistroRulesVersion, 192 distroStatus, installedDistroRulesVersion); 193 } 194 } 195 196 @Override 197 public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor, 198 byte[] checkTokenBytes, ICallback callback) { 199 200 boolean closeParcelFileDescriptorOnExit = true; 201 try { 202 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 203 204 CheckToken checkToken = null; 205 if (checkTokenBytes != null) { 206 checkToken = createCheckTokenOrThrow(checkTokenBytes); 207 } 208 EventLogTags.writeTimezoneRequestInstall(toStringOrNull(checkToken)); 209 210 synchronized (this) { 211 if (distroParcelFileDescriptor == null) { 212 throw new NullPointerException("distroParcelFileDescriptor == null"); 213 } 214 if (callback == null) { 215 throw new NullPointerException("observer == null"); 216 } 217 if (mOperationInProgress.get()) { 218 return RulesManager.ERROR_OPERATION_IN_PROGRESS; 219 } 220 mOperationInProgress.set(true); 221 222 // Execute the install asynchronously. 223 mExecutor.execute( 224 new InstallRunnable(distroParcelFileDescriptor, checkToken, callback)); 225 226 // The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after 227 // it executes (and we do not have to). 228 closeParcelFileDescriptorOnExit = false; 229 230 return RulesManager.SUCCESS; 231 } 232 } finally { 233 // We should close() the local ParcelFileDescriptor we were passed if it hasn't been 234 // passed to another thread to handle. 235 if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) { 236 try { 237 distroParcelFileDescriptor.close(); 238 } catch (IOException e) { 239 Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e); 240 } 241 } 242 } 243 } 244 245 private class InstallRunnable implements Runnable { 246 247 private final ParcelFileDescriptor mDistroParcelFileDescriptor; 248 private final CheckToken mCheckToken; 249 private final ICallback mCallback; 250 251 InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken, 252 ICallback callback) { 253 mDistroParcelFileDescriptor = distroParcelFileDescriptor; 254 mCheckToken = checkToken; 255 mCallback = callback; 256 } 257 258 @Override 259 public void run() { 260 EventLogTags.writeTimezoneInstallStarted(toStringOrNull(mCheckToken)); 261 262 boolean success = false; 263 // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed 264 // when we are done. 265 try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) { 266 // The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close 267 // it at the end of the try-with-resources. 268 final boolean isFdOwner = false; 269 InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner); 270 271 TimeZoneDistro distro = new TimeZoneDistro(is); 272 int installerResult = mInstaller.stageInstallWithErrorCode(distro); 273 int resultCode = mapInstallerResultToApiCode(installerResult); 274 EventLogTags.writeTimezoneInstallComplete(toStringOrNull(mCheckToken), resultCode); 275 sendFinishedStatus(mCallback, resultCode); 276 277 // All the installer failure modes are currently non-recoverable and won't be 278 // improved by trying again. Therefore success = true. 279 success = true; 280 } catch (Exception e) { 281 Slog.w(TAG, "Failed to install distro.", e); 282 EventLogTags.writeTimezoneInstallComplete( 283 toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE); 284 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE); 285 } finally { 286 // Notify the package tracker that the operation is now complete. 287 mPackageTracker.recordCheckResult(mCheckToken, success); 288 289 mOperationInProgress.set(false); 290 } 291 } 292 293 private int mapInstallerResultToApiCode(int installerResult) { 294 switch (installerResult) { 295 case TimeZoneDistroInstaller.INSTALL_SUCCESS: 296 return Callback.SUCCESS; 297 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE: 298 return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE; 299 case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD: 300 return Callback.ERROR_INSTALL_RULES_TOO_OLD; 301 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION: 302 return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION; 303 case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR: 304 return Callback.ERROR_INSTALL_VALIDATION_ERROR; 305 default: 306 return Callback.ERROR_UNKNOWN_FAILURE; 307 } 308 } 309 } 310 311 @Override 312 public int requestUninstall(byte[] checkTokenBytes, ICallback callback) { 313 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 314 315 CheckToken checkToken = null; 316 if (checkTokenBytes != null) { 317 checkToken = createCheckTokenOrThrow(checkTokenBytes); 318 } 319 EventLogTags.writeTimezoneRequestUninstall(toStringOrNull(checkToken)); 320 synchronized(this) { 321 if (callback == null) { 322 throw new NullPointerException("callback == null"); 323 } 324 325 if (mOperationInProgress.get()) { 326 return RulesManager.ERROR_OPERATION_IN_PROGRESS; 327 } 328 mOperationInProgress.set(true); 329 330 // Execute the uninstall asynchronously. 331 mExecutor.execute(new UninstallRunnable(checkToken, callback)); 332 333 return RulesManager.SUCCESS; 334 } 335 } 336 337 private class UninstallRunnable implements Runnable { 338 339 private final CheckToken mCheckToken; 340 private final ICallback mCallback; 341 342 UninstallRunnable(CheckToken checkToken, ICallback callback) { 343 mCheckToken = checkToken; 344 mCallback = callback; 345 } 346 347 @Override 348 public void run() { 349 EventLogTags.writeTimezoneUninstallStarted(toStringOrNull(mCheckToken)); 350 boolean packageTrackerStatus = false; 351 try { 352 int uninstallResult = mInstaller.stageUninstall(); 353 packageTrackerStatus = (uninstallResult == TimeZoneDistroInstaller.UNINSTALL_SUCCESS 354 || uninstallResult == TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED); 355 356 // Right now we just have Callback.SUCCESS / Callback.ERROR_UNKNOWN_FAILURE for 357 // uninstall. All clients should be checking against SUCCESS. More granular failures 358 // may be added in future. 359 int callbackResultCode = 360 packageTrackerStatus ? Callback.SUCCESS : Callback.ERROR_UNKNOWN_FAILURE; 361 EventLogTags.writeTimezoneUninstallComplete( 362 toStringOrNull(mCheckToken), callbackResultCode); 363 sendFinishedStatus(mCallback, callbackResultCode); 364 } catch (Exception e) { 365 EventLogTags.writeTimezoneUninstallComplete( 366 toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE); 367 Slog.w(TAG, "Failed to uninstall distro.", e); 368 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE); 369 } finally { 370 // Notify the package tracker that the operation is now complete. 371 mPackageTracker.recordCheckResult(mCheckToken, packageTrackerStatus); 372 373 mOperationInProgress.set(false); 374 } 375 } 376 } 377 378 private void sendFinishedStatus(ICallback callback, int resultCode) { 379 try { 380 callback.onFinished(resultCode); 381 } catch (RemoteException e) { 382 Slog.e(TAG, "Unable to notify observer of result", e); 383 } 384 } 385 386 @Override 387 public void requestNothing(byte[] checkTokenBytes, boolean success) { 388 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 389 CheckToken checkToken = null; 390 if (checkTokenBytes != null) { 391 checkToken = createCheckTokenOrThrow(checkTokenBytes); 392 } 393 EventLogTags.writeTimezoneRequestNothing(toStringOrNull(checkToken)); 394 mPackageTracker.recordCheckResult(checkToken, success); 395 EventLogTags.writeTimezoneNothingComplete(toStringOrNull(checkToken)); 396 } 397 398 @Override 399 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 400 if (!mPermissionHelper.checkDumpPermission(TAG, pw)) { 401 return; 402 } 403 404 RulesState rulesState = getRulesStateInternal(); 405 if (args != null && args.length == 2) { 406 // Formatting options used for automated tests. The format is less free-form than 407 // the -format options, which are intended to be easier to parse. 408 if ("-format_state".equals(args[0]) && args[1] != null) { 409 for (char c : args[1].toCharArray()) { 410 switch (c) { 411 case 'p': { 412 // Report operation in progress 413 String value = "Unknown"; 414 if (rulesState != null) { 415 value = Boolean.toString(rulesState.isOperationInProgress()); 416 } 417 pw.println("Operation in progress: " + value); 418 break; 419 } 420 case 's': { 421 // Report system image rules version 422 String value = "Unknown"; 423 if (rulesState != null) { 424 value = rulesState.getSystemRulesVersion(); 425 } 426 pw.println("System rules version: " + value); 427 break; 428 } 429 case 'c': { 430 // Report current installation state 431 String value = "Unknown"; 432 if (rulesState != null) { 433 value = distroStatusToString(rulesState.getDistroStatus()); 434 } 435 pw.println("Current install state: " + value); 436 break; 437 } 438 case 'i': { 439 // Report currently installed version 440 String value = "Unknown"; 441 if (rulesState != null) { 442 DistroRulesVersion installedRulesVersion = 443 rulesState.getInstalledDistroRulesVersion(); 444 if (installedRulesVersion == null) { 445 value = "<None>"; 446 } else { 447 value = installedRulesVersion.toDumpString(); 448 } 449 } 450 pw.println("Installed rules version: " + value); 451 break; 452 } 453 case 'o': { 454 // Report staged operation type 455 String value = "Unknown"; 456 if (rulesState != null) { 457 int stagedOperationType = rulesState.getStagedOperationType(); 458 value = stagedOperationToString(stagedOperationType); 459 } 460 pw.println("Staged operation: " + value); 461 break; 462 } 463 case 't': { 464 // Report staged version (i.e. the one that will be installed next boot 465 // if the staged operation is an install). 466 String value = "Unknown"; 467 if (rulesState != null) { 468 DistroRulesVersion stagedDistroRulesVersion = 469 rulesState.getStagedDistroRulesVersion(); 470 if (stagedDistroRulesVersion == null) { 471 value = "<None>"; 472 } else { 473 value = stagedDistroRulesVersion.toDumpString(); 474 } 475 } 476 pw.println("Staged rules version: " + value); 477 break; 478 } 479 case 'a': { 480 // Report the active rules version (i.e. the rules in use by the current 481 // process). 482 pw.println("Active rules version (ICU, libcore): " 483 + ICU.getTZDataVersion() + "," 484 + ZoneInfoDB.getInstance().getVersion()); 485 break; 486 } 487 default: { 488 pw.println("Unknown option: " + c); 489 } 490 } 491 } 492 return; 493 } 494 } 495 496 pw.println("RulesManagerService state: " + toString()); 497 pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + "," 498 + ZoneInfoDB.getInstance().getVersion()); 499 pw.println("Distro state: " + rulesState.toString()); 500 mPackageTracker.dump(pw); 501 } 502 503 /** 504 * Called when the device is considered idle. 505 */ 506 void notifyIdle() { 507 // No package has changed: we are just triggering because the device is idle and there 508 // *might* be work to do. 509 final boolean packageChanged = false; 510 mPackageTracker.triggerUpdateIfNeeded(packageChanged); 511 } 512 513 @Override 514 public String toString() { 515 return "RulesManagerService{" + 516 "mOperationInProgress=" + mOperationInProgress + 517 '}'; 518 } 519 520 private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) { 521 CheckToken checkToken; 522 try { 523 checkToken = CheckToken.fromByteArray(checkTokenBytes); 524 } catch (IOException e) { 525 throw new IllegalArgumentException("Unable to read token bytes " 526 + Arrays.toString(checkTokenBytes), e); 527 } 528 return checkToken; 529 } 530 531 private static String distroStatusToString(int distroStatus) { 532 switch(distroStatus) { 533 case DISTRO_STATUS_NONE: 534 return "None"; 535 case DISTRO_STATUS_INSTALLED: 536 return "Installed"; 537 case DISTRO_STATUS_UNKNOWN: 538 default: 539 return "Unknown"; 540 } 541 } 542 543 private static String stagedOperationToString(int stagedOperationType) { 544 switch(stagedOperationType) { 545 case STAGED_OPERATION_NONE: 546 return "None"; 547 case STAGED_OPERATION_UNINSTALL: 548 return "Uninstall"; 549 case STAGED_OPERATION_INSTALL: 550 return "Install"; 551 case STAGED_OPERATION_UNKNOWN: 552 default: 553 return "Unknown"; 554 } 555 } 556 557 private static String toStringOrNull(Object obj) { 558 return obj == null ? null : obj.toString(); 559 } 560 } 561