blob: c9e564a0ce9d988433093a4be85d41f8465c4843 [file] [log] [blame]
/*
* 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.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.IBinder;
import android.os.IIdmap2;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemService;
import android.text.TextUtils;
import android.util.Slog;
import com.android.server.FgThread;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
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 int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 5;
private static final String IDMAP_DAEMON = "idmap2d";
private static IdmapDaemon sInstance;
private volatile IIdmap2 mService;
private final AtomicInteger mOpenedCount = new AtomicInteger();
private final Object mIdmapToken = new Object();
/**
* 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 (mIdmapToken) {
mOpenedCount.incrementAndGet();
}
}
@Override
public void close() {
synchronized (mIdmapToken) {
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 (mIdmapToken) {
// Only stop the service if the service does not have an open connection.
if (mService == null || mOpenedCount.get() != 0) {
return;
}
stopIdmapService();
mService = null;
}
}, mIdmapToken, SERVICE_TIMEOUT_MS);
}
}
}
static IdmapDaemon getInstance() {
if (sInstance == null) {
sInstance = new IdmapDaemon();
}
return sInstance;
}
String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
@Nullable String overlayName, int policies, boolean enforce, int userId)
throws TimeoutException, RemoteException {
try (Connection c = connect()) {
return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
policies, enforce, userId);
}
}
boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException {
try (Connection c = connect()) {
return mService.removeIdmap(overlayPath, userId);
}
}
boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
@Nullable String overlayName, int policies, boolean enforce, int userId)
throws Exception {
try (Connection c = connect()) {
return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
policies, enforce, userId);
}
}
boolean idmapExists(String overlayPath, int userId) {
try (Connection c = connect()) {
return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
} catch (Exception e) {
Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
return false;
}
}
FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
try (Connection c = connect()) {
return mService.createFabricatedOverlay(overlay);
} catch (Exception e) {
Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
return null;
}
}
boolean deleteFabricatedOverlay(@NonNull String path) {
try (Connection c = connect()) {
return mService.deleteFabricatedOverlay(path);
} catch (Exception e) {
Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
return false;
}
}
synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
try (Connection c = connect()) {
mService.acquireFabricatedOverlayIterator();
List<FabricatedOverlayInfo> infos;
while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) {
allInfos.addAll(infos);
}
return allInfos;
} catch (Exception e) {
Slog.wtf(TAG, "failed to get all fabricated overlays", e);
} finally {
try {
mService.releaseFabricatedOverlayIterator();
} catch (RemoteException e) {
// ignore
}
}
return allInfos;
}
String dumpIdmap(@NonNull String overlayPath) {
try (Connection c = connect()) {
String dump = mService.dumpIdmap(overlayPath);
return TextUtils.nullIfEmpty(dump);
} catch (Exception e) {
Slog.wtf(TAG, "failed to dump idmap", e);
return null;
}
}
private IBinder getIdmapService() throws TimeoutException, RemoteException {
SystemService.start(IDMAP_DAEMON);
final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS;
while (SystemClock.elapsedRealtime() <= endMillis) {
final IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
if (binder != null) {
binder.linkToDeath(
() -> Slog.w(TAG, String.format("service '%s' died", IDMAP_SERVICE)), 0);
return binder;
}
try {
Thread.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
} catch (InterruptedException ignored) {
}
}
throw new TimeoutException(
String.format("Failed to connect to '%s' in %d milliseconds", IDMAP_SERVICE,
SERVICE_CONNECT_TIMEOUT_MS));
}
private static void stopIdmapService() {
try {
SystemService.stop(IDMAP_DAEMON);
} catch (RuntimeException e) {
// If the idmap daemon cannot be disabled for some reason, it is okay
// since we already finished invoking idmap.
Slog.w(TAG, "Failed to disable idmap2 daemon", e);
}
}
private Connection connect() throws TimeoutException, RemoteException {
synchronized (mIdmapToken) {
FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken);
if (mService != null) {
// Not enough time has passed to stop the idmap service. Reuse the existing
// interface.
return new Connection();
}
mService = IIdmap2.Stub.asInterface(getIdmapService());
return new Connection();
}
}
}