Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2b5140d3 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

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: I38a46e034d56777c15d1b3a5c86a9313ecaf0d4c
parent 4c452a65
Loading
Loading
Loading
Loading
+193 −0
Original line number Diff line number Diff line
/*
 * 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.IoThread;

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;
                }

                IoThread.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) {
            IoThread.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<>(() -> {
                IBinder binder = null;
                while (binder == null) {
                    try {
                        binder = ServiceManager.getService(IDMAP_SERVICE);
                        Thread.sleep(100);
                    } catch (Exception e) {
                        Slog.e(TAG, "service '" + IDMAP_SERVICE + "' not retrieved; "
                                + e.getMessage());
                    }
                }
                return binder;
            });

            IBinder binder;
            try {
                IoThread.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();
        }
    }
}
+10 −49
Original line number Diff line number Diff line
@@ -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.om.OverlayInfo;
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 @@ import java.io.File;
 */
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 @@ class IdmapManager {
        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 @@ class IdmapManager {
            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 @@ class IdmapManager {
        }
        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 @@ class IdmapManager {
            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 @@ class IdmapManager {
        }
    }

    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.
+1 −0
Original line number Diff line number Diff line
@@ -262,6 +262,7 @@ public final class OverlayManagerService extends SystemService {

            initIfNeeded();
            onSwitchUser(UserHandle.USER_SYSTEM);
            IdmapDaemon.stopIdmapService();

            publishBinderService(Context.OVERLAY_SERVICE, mService);
            publishLocalService(OverlayManagerService.class, this);