Stop idmap2d after several seconds pass
Idmap2d is continuously running despite invocations to generate idmaps
being rare. This change stops the idmap daemon after 10 seconds without
a transaction.
Bug: 135149444
Test: manual verifications that idmaps are created and then service
dies.
Change-Id: Ic9e131b9b4091d114ce2727ce48ff467469a3f0b
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
new file mode 100644
index 0000000..91824c3
--- /dev/null
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om;
+
+import static android.content.Context.IDMAP_SERVICE;
+
+import static com.android.server.om.OverlayManagerService.DEBUG;
+import static com.android.server.om.OverlayManagerService.TAG;
+
+import android.os.IBinder;
+import android.os.IIdmap2;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.server.FgThread;
+
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10
+ * seconds without a transaction.
+ **/
+class IdmapDaemon {
+ // The amount of time in milliseconds to wait after a transaction to the idmap service is made
+ // before stopping the service.
+ private static final int SERVICE_TIMEOUT_MS = 10000;
+
+ // The amount of time in milliseconds to wait when attempting to connect to idmap service.
+ private static final int SERVICE_CONNECT_TIMEOUT_MS = 5000;
+
+ private static final Object IDMAP_TOKEN = new Object();
+ private static final String IDMAP_DAEMON = "idmap2d";
+
+ private static IdmapDaemon sInstance;
+ private volatile IIdmap2 mService;
+ private final AtomicInteger mOpenedCount = new AtomicInteger();
+
+ /**
+ * An {@link AutoCloseable} connection to the idmap service. When the connection is closed or
+ * finalized, the idmap service will be stopped after a period of time unless another connection
+ * to the service is open.
+ **/
+ private class Connection implements AutoCloseable {
+ private boolean mOpened = true;
+
+ private Connection() {
+ synchronized (IDMAP_TOKEN) {
+ mOpenedCount.incrementAndGet();
+ }
+ }
+
+ @Override
+ public void close() {
+ synchronized (IDMAP_TOKEN) {
+ if (!mOpened) {
+ return;
+ }
+
+ mOpened = false;
+ if (mOpenedCount.decrementAndGet() != 0) {
+ // Only post the callback to stop the service if the service does not have an
+ // open connection.
+ return;
+ }
+
+ FgThread.getHandler().postDelayed(() -> {
+ synchronized (IDMAP_TOKEN) {
+ // Only stop the service if the service does not have an open connection.
+ if (mService == null || mOpenedCount.get() != 0) {
+ return;
+ }
+
+ stopIdmapService();
+ mService = null;
+ }
+ }, IDMAP_TOKEN, SERVICE_TIMEOUT_MS);
+ }
+ }
+ }
+
+ static IdmapDaemon getInstance() {
+ if (sInstance == null) {
+ sInstance = new IdmapDaemon();
+ }
+ return sInstance;
+ }
+
+ String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
+ int userId) throws Exception {
+ try (Connection connection = connect()) {
+ return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
+ }
+ }
+
+ boolean removeIdmap(String overlayPath, int userId) throws Exception {
+ try (Connection connection = connect()) {
+ return mService.removeIdmap(overlayPath, userId);
+ }
+ }
+
+ boolean verifyIdmap(String overlayPath, int policies, boolean enforce, int userId)
+ throws Exception {
+ try (Connection connection = connect()) {
+ return mService.verifyIdmap(overlayPath, policies, enforce, userId);
+ }
+ }
+
+ String getIdmapPath(String overlayPath, int userId) throws Exception {
+ try (Connection connection = connect()) {
+ return mService.getIdmapPath(overlayPath, userId);
+ }
+ }
+
+ static void startIdmapService() {
+ SystemProperties.set("ctl.start", IDMAP_DAEMON);
+ }
+
+ static void stopIdmapService() {
+ SystemProperties.set("ctl.stop", IDMAP_DAEMON);
+ }
+
+ private Connection connect() throws Exception {
+ synchronized (IDMAP_TOKEN) {
+ FgThread.getHandler().removeCallbacksAndMessages(IDMAP_TOKEN);
+ if (mService != null) {
+ // Not enough time has passed to stop the idmap service. Reuse the existing
+ // interface.
+ return new Connection();
+ }
+
+ // Start the idmap service if it is not currently running.
+ startIdmapService();
+
+ // Block until the service is found.
+ FutureTask<IBinder> bindIdmap = new FutureTask<>(() -> {
+ while (true) {
+ try {
+ IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
+ if (binder != null) {
+ return binder;
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "service '" + IDMAP_SERVICE + "' not retrieved; "
+ + e.getMessage());
+ }
+ Thread.sleep(100);
+ }
+ });
+
+ IBinder binder;
+ try {
+ FgThread.getHandler().postAtFrontOfQueue(bindIdmap);
+ binder = bindIdmap.get(SERVICE_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (Exception rethrow) {
+ Slog.e(TAG, "service '" + IDMAP_SERVICE + "' not found;");
+ throw rethrow;
+ }
+
+ try {
+ binder.linkToDeath(() -> {
+ Slog.w(TAG, "service '" + IDMAP_SERVICE + "' died");
+ }, 0);
+ } catch (RemoteException rethrow) {
+ Slog.e(TAG, "service '" + IDMAP_SERVICE + "' failed to be bound");
+ throw rethrow;
+ }
+
+ mService = IIdmap2.Stub.asInterface(binder);
+ if (DEBUG) {
+ Slog.d(TAG, "service '" + IDMAP_SERVICE + "' connected");
+ }
+
+ return new Connection();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index b604aa8..9b6d986 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -16,9 +16,6 @@
package com.android.server.om;
-import static android.content.Context.IDMAP_SERVICE;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-
import static com.android.server.om.OverlayManagerService.DEBUG;
import static com.android.server.om.OverlayManagerService.TAG;
@@ -27,15 +24,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build.VERSION_CODES;
-import android.os.IBinder;
import android.os.IIdmap2;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
-import com.android.internal.os.BackgroundThread;
import com.android.server.om.OverlayManagerServiceImpl.PackageManagerHelper;
import com.android.server.pm.Installer;
@@ -51,11 +44,6 @@
*/
class IdmapManager {
private static final boolean FEATURE_FLAG_IDMAP2 = true;
-
- private final Installer mInstaller;
- private final PackageManagerHelper mPackageManager;
- private IIdmap2 mIdmap2Service;
-
private static final boolean VENDOR_IS_Q_OR_LATER;
static {
final String value = SystemProperties.get("ro.vndk.version", "29");
@@ -70,12 +58,14 @@
VENDOR_IS_Q_OR_LATER = isQOrLater;
}
+ private final Installer mInstaller;
+ private final PackageManagerHelper mPackageManager;
+ private final IdmapDaemon mIdmapDaemon;
+
IdmapManager(final Installer installer, final PackageManagerHelper packageManager) {
mInstaller = installer;
mPackageManager = packageManager;
- if (FEATURE_FLAG_IDMAP2) {
- connectToIdmap2d();
- }
+ mIdmapDaemon = IdmapDaemon.getInstance();
}
boolean createIdmap(@NonNull final PackageInfo targetPackage,
@@ -91,11 +81,11 @@
if (FEATURE_FLAG_IDMAP2) {
int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
boolean enforce = enforceOverlayable(overlayPackage);
- if (mIdmap2Service.verifyIdmap(overlayPath, policies, enforce, userId)) {
+ if (mIdmapDaemon.verifyIdmap(overlayPath, policies, enforce, userId)) {
return true;
}
- return mIdmap2Service.createIdmap(targetPath, overlayPath, policies, enforce,
- userId) != null;
+ return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+ enforce, userId) != null;
} else {
mInstaller.idmap(targetPath, overlayPath, sharedGid);
return true;
@@ -113,7 +103,7 @@
}
try {
if (FEATURE_FLAG_IDMAP2) {
- return mIdmap2Service.removeIdmap(oi.baseCodePath, userId);
+ return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
} else {
mInstaller.removeIdmap(oi.baseCodePath);
return true;
@@ -137,7 +127,7 @@
final int userId) {
if (FEATURE_FLAG_IDMAP2) {
try {
- return mIdmap2Service.getIdmapPath(overlayPackagePath, userId);
+ return mIdmapDaemon.getIdmapPath(overlayPackagePath, userId);
} catch (Exception e) {
Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
+ e.getMessage());
@@ -151,35 +141,6 @@
}
}
- private void connectToIdmap2d() {
- IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
- if (binder != null) {
- try {
- binder.linkToDeath(new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Slog.w(TAG, "service '" + IDMAP_SERVICE + "' died; reconnecting...");
- connectToIdmap2d();
- }
-
- }, 0);
- } catch (RemoteException e) {
- binder = null;
- }
- }
- if (binder != null) {
- mIdmap2Service = IIdmap2.Stub.asInterface(binder);
- if (DEBUG) {
- Slog.d(TAG, "service '" + IDMAP_SERVICE + "' connected");
- }
- } else {
- Slog.w(TAG, "service '" + IDMAP_SERVICE + "' not found; trying again...");
- BackgroundThread.getHandler().postDelayed(() -> {
- connectToIdmap2d();
- }, SECOND_IN_MILLIS);
- }
- }
-
/**
* Checks if overlayable and policies should be enforced on the specified overlay for backwards
* compatibility with pre-Q overlays.
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index da69986..ce95181 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -262,6 +262,7 @@
initIfNeeded();
onSwitchUser(UserHandle.USER_SYSTEM);
+ IdmapDaemon.stopIdmapService();
publishBinderService(Context.OVERLAY_SERVICE, mService);
publishLocalService(OverlayManagerService.class, this);